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 imagingbook.core.FileUtils;
012
013import java.io.File;
014import java.io.IOException;
015import java.lang.reflect.Field;
016import java.net.URI;
017import java.net.URISyntaxException;
018import java.net.URL;
019import java.nio.file.FileSystems;
020import java.nio.file.Files;
021import java.nio.file.Path;
022import java.nio.file.Paths;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.Comparator;
027import java.util.List;
028import java.util.Objects;
029import java.util.stream.Stream;
030
031/**
032 * Static utility methods related to classes.
033 *
034 * @author WB
035 * @version 2022/11/20
036 *
037 */
038public abstract class ClassUtils {
039        
040        private ClassUtils() {}
041
042        /**
043         * Lists all accessible public fields of the given object and returns the result as a string.
044         *
045         * @param obj a (non-null) object
046         * @return a string listing the names and values of the object's fields
047         */
048        public static String listFields(Object obj) {
049                Class<?> clz = obj.getClass();
050                StringBuilder buf = new StringBuilder(clz.getName() + ":\n");
051                Field[] fields = clz.getFields();
052                for (Field f : fields) {
053                        String name = f.getName();
054                        String value = null;
055                        try {
056                                value = f.get(obj).toString();
057                        } catch (IllegalArgumentException | IllegalAccessException e1) {                        }
058                        buf.append(name + ": " + value + "\n");
059                }
060                return buf.toString();
061        }
062
063        /**
064         * Collects and returns the list of classes defined in the specified package. The {@link Package} may be obtained
065         * from an existing {@link Class} object ({@code clazz}) by {@code clazz.getPackage()}.
066         *
067         * @param pkg a {@link Package} instance, e.g., {@code Package.getPackage("imagingbook.lib.ij.overlay")}
068         * @return a list of classes contained in the package
069         */
070        public static List<Class<?>> getClassesInPackage(Package pkg) {
071                return getClassesInPackage(pkg.getName());
072        }
073
074        /**
075         * Collects and returns the list of classes defined in the specified package. Adapted from
076         * https://stackoverflow.com/a/58773271
077         *
078         * @param pkgName the full package name, e.g., "imagingbook.lib.ij.overlay"
079         * @return a list of classes contained in the package
080         */
081        public static List<Class<?>> getClassesInPackage(final String pkgName) {
082                System.out.println("pkgName = " + pkgName);
083                final String pkgPath = pkgName.replace('.', '/');
084                System.out.println("pkgPath = " + pkgPath);
085                
086                URL pkgURL = ClassLoader.getSystemClassLoader().getResource(pkgPath);
087                System.out.println("URL = " + pkgURL);
088                
089                URI pkgURI = null;
090                try {
091                        pkgURI = Objects.requireNonNull(pkgURL.toURI());
092                } catch (URISyntaxException e) {
093                        throw new RuntimeException(e.getMessage());
094                }
095                System.out.println("URI = " + pkgURI);
096        
097                Path root = null;
098                if (pkgURI.toString().startsWith("jar:")) {
099                        root = FileSystems.getFileSystem(pkgURI).getPath(pkgPath);
100                        if (root == null) {
101                                try {
102                                        root = FileSystems.newFileSystem(pkgURI, Collections.emptyMap()).getPath(pkgPath);
103                                } catch (IOException e) {
104                                        throw new RuntimeException(e.getMessage());
105                                }
106                        }
107                } else {
108                        root = Paths.get(pkgURI);
109                }
110        
111                final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();
112                try (final Stream<Path> allPaths = Files.walk(root)) {
113                        allPaths.filter(Files::isRegularFile).forEach(path -> {
114                                String pathName = path.toString();
115                                //System.out.println("pathName = " + pathName);
116                                if (FileUtils.getFileExtension(pathName).equals("class")) {
117                                        String className = FileUtils.stripFileExtension(pathName);
118                                        System.out.println("className = " + className);
119                                        className = className.replace(File.separatorChar, '.');
120                                        className = className.substring(className.indexOf(pkgName));
121                                        try {           
122                                                allClasses.add(Class.forName(className));
123                                        } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
124                                        }
125                                }
126                        });
127                } catch (IOException e) {
128                        throw new RuntimeException(e.getMessage());
129                }
130                return allClasses;
131        }
132
133        /**
134         * Returns an array of all constants defined by the specified enum class, sorted by their names.
135         *
136         * @param enumClass the enum class
137         * @return an array of enum items sorted by names
138         * @param <E> generic type
139         */
140        public static <E extends Enum<E>> E[] getEnumConstantsSorted(Class<E> enumClass) {
141                E[] items = enumClass.getEnumConstants();
142                // compare by constant names (case sensitive):
143                Comparator<E> cpr = new Comparator<>() {
144                        public int compare(E e1, E e2) {
145                                return e1.name().compareTo(e2.name());
146                        }
147                };
148                Arrays.sort(items, cpr);
149                return items;
150        }
151
152        /**
153         * Returns the "natural order" {@link Comparator} instance for the specified {@link Comparable} class. Use
154         * {@link Comparator#reversed()} to obtain the comparator for sorting in reverse order.
155         *
156         * @param clazz a class implementing {@link Comparable}
157         * @param <T> the generic type
158         * @return the associated {@link Comparator} instance
159         */
160        public static <T extends Comparable> Comparator<T> getComparator(Class<T> clazz) {
161                return Comparator.naturalOrder();
162        }
163}