001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009package imagingbook.common.util;
010
011import ij.gui.GenericDialog;
012import imagingbook.common.ij.DialogUtils;
013
014import java.io.ByteArrayOutputStream;
015import java.io.PrintStream;
016import java.lang.reflect.Field;
017import java.lang.reflect.Modifier;
018import java.util.ArrayList;
019import java.util.List;
020
021/**
022 * <p>
023 * Interface to be implemented by local 'Parameters' classes. This is part of the 'simple parameter object' scheme,
024 * working with public fields. Only non-static, non-final, public fields are accepted as parameters. Current features
025 * include: <br> (a) Makes parameter bundles printable by listing all eligible fields. <br> (b) Parameter bundles can be
026 * added/modified as a whole by ImageJ's {@link GenericDialog}, supported by specific annotations (use methods
027 * {@link DialogUtils#addToDialog(ParameterBundle, GenericDialog)} and
028 * {@link DialogUtils#getFromDialog(ParameterBundle, GenericDialog)}). <br> See the example in {@code DemoParameters}
029 * below. Other functionality may be added in the future.
030 * </p>
031 * <pre>
032 * public class ClassToBeParameterized {
033 *      enum MyEnum {  // local enum type
034 *              A, B, Cee
035 *    };
036 *      // Sample parameter bundle class:
037 *      static class DemoParameters implements ParameterBundle&lt;ClassToBeParameterized&gt; {
038 *              public static int staticInt = 44; // currently static members are listed too!
039 *              &#64;DialogLabel("Make a decision:")
040 *              public boolean someBool = true;
041 *              public int someInt = 39;
042 *              public float someFloat = 1.99f;
043 *              &#64;DialogLabel("Math.PI")
044 *              &#64;DialogDigits(10)
045 *              public double someDouble = Math.PI;
046 *              public String someString = "SHOW ME";
047 *              &#64;DialogHide
048 *              public String hiddenString = "HIDE ME";
049 *              public MyEnum someEnum = MyEnum.B;
050 *    }
051 *      public static void main(String[] args) {
052 *              ParameterBundle params = new DemoParameters();
053 *              System.out.println("p1 = \n" + params.printToString());
054 *              GenericDialog gd = new GenericDialog(ParameterBundle.class.getSimpleName());
055 *              gd.addNumericField("some single int", 123, 0);
056 *              params.addToDialog(gd);
057 *              gd.showDialog();
058 *              if (gd.wasCanceled())
059 *                      return;
060 *              int singleInt = (int) gd.getNextNumber();
061 *              boolean success = params.getFromDialog(gd);
062 *              System.out.println("success = " + success);
063 *              System.out.println("p2 = \n" + params.printToString());
064 *    }
065 * }
066 * </pre>
067 *
068 * @param <TargetT> the target class to be parameterized
069 * @author WB
070 * @version 2022/11/23 added generic target type
071 * @see imagingbook.common.ij.DialogUtils.DialogDigits
072 * @see imagingbook.common.ij.DialogUtils.DialogLabel
073 * @see imagingbook.common.ij.DialogUtils.DialogHide
074 */
075public interface ParameterBundle<TargetT> {
076        
077        /**
078         * Returns the valid parameter fields as an array. 
079         * @return the valid parameter fields
080         */
081        default Field[] getValidParameterFields() {
082                Class<?> clazz = this.getClass();
083                List<Field> validFields = new ArrayList<>();
084                for (Field f : clazz.getFields()) {
085                        if (isValidParameterField(f)) {
086                                validFields.add(f);
087                        }
088                }
089                return validFields.toArray(new Field[0]);
090        }
091
092        /**
093         * Substitute for {@link Object#toString()}, which cannot be overridden by an interface's default method.
094         *
095         * @return as string representation of theis parameter bundle
096         */
097        default String printToString() {
098                ByteArrayOutputStream bas = new ByteArrayOutputStream();
099                try (PrintStream strm = new PrintStream(bas)) {
100                        printToStream(strm);
101                }
102                return bas.toString();
103        }
104
105        /**
106         * Sends a string representation of this parameter bundle to the specified stream.
107         *
108         * @param strm the output stream
109         */
110        default void printToStream(PrintStream strm) {
111                Class<?> clazz = this.getClass();
112                if (!Modifier.isPublic(clazz.getModifiers())) {
113                        strm.print("[WARNING] class " + clazz.getSimpleName() + " should be declared public or protected!\n");
114                }
115                Field[] fields = clazz.getFields();             // gets only public fields
116//              strm.println(clazz.getCanonicalName());
117                for (Field field : fields) {
118//                      if (!isValidParameterItem(field)) {
119//                              continue;
120//                      }
121                        strm.print(field.getType().getSimpleName() + " ");
122                        strm.print(field.getName() + " = ");
123                        try {
124                                strm.print(field.get(this).toString());
125                        } catch (IllegalArgumentException | IllegalAccessException e) { 
126                                strm.print("FIELD VALUE UNREADABLE!");
127                        }       
128                        strm.println();
129//                      int modifiers = field.getModifiers();
130//                      strm.println("Field is public = " + Modifier.isPublic(modifiers));
131//                      strm.println("Field is final = " + Modifier.isFinal(modifiers));
132                }
133        }
134
135        /**
136         * Validates the correctness and compatibility of the parameters in this bundle. Does nothing by default,
137         * implementing classes should override this method.
138         *
139         * @return true if all parameters are OK, false otherwise
140         */
141        default boolean validate() {
142                return true;
143        }
144
145        /**
146         * Returns true iff the specified field is a valid parameter item. This applies if the field is neither private nor
147         * final or static.
148         *
149         * @param f the field
150         * @return true if a valid parameter field
151         */
152        static boolean isValidParameterField(Field f) {
153                int mod = f.getModifiers();
154                if (Modifier.isPrivate(mod) || Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
155                        return false;
156                }
157                else {
158                        return true;
159                }
160        }
161
162//      static void printModifiers(Field f) {
163//              int mod = f.getModifiers();
164//              System.out.println("Modifiers of field " + f.getName());
165//              System.out.println("abstract     = " + Modifier.isAbstract(mod));
166//              System.out.println("final        = " + Modifier.isFinal(mod));
167//              System.out.println("interface    = " + Modifier.isInterface(mod));
168//              System.out.println("native       = " + Modifier.isNative(mod));
169//              System.out.println("private      = " + Modifier.isPrivate(mod));
170//              System.out.println("protected    = " + Modifier.isProtected(mod));
171//              System.out.println("public       = " + Modifier.isPublic(mod));
172//              System.out.println("static       = " + Modifier.isStatic(mod));
173//              System.out.println("strict       = " + Modifier.isStrict(mod));
174//              System.out.println("synchronized = " + Modifier.isSynchronized(mod));
175//              System.out.println("transient    = " + Modifier.isTransient(mod));
176//              System.out.println("volatite     = " + Modifier.isVolatile(mod));
177//      }
178
179        
180        /**
181         * Returns a shallow copy of the specified {@link ParameterBundle}
182         * instance.
183         * 
184         * @param <T> generic type
185         * @param params a {@link ParameterBundle} instance
186         * @return a copy with the same type, fields and values as the original instance
187         */
188        public static <T extends ParameterBundle<?>> T duplicate(T params) {
189            return ObjectUtils.copy(params);
190        }
191
192}