From 0d841c6f06711333b830b37806fb9b43042ffa70 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:20:34 +0100 Subject: [PATCH 01/10] update date --- src/bzh/plealog/dbmirror/main/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bzh/plealog/dbmirror/main/version.properties b/src/bzh/plealog/dbmirror/main/version.properties index 0fb3fd8..d328750 100644 --- a/src/bzh/plealog/dbmirror/main/version.properties +++ b/src/bzh/plealog/dbmirror/main/version.properties @@ -5,7 +5,7 @@ prg.app.name=BeeDeeM # releases prior to 4: KoriBlast/ngKLAST suite of software prg.version=5.0.0 # Who did what and when -prg.copyright=(c) 2007-2022, Patrick G. Durand +prg.copyright=(c) 2007-2023, Patrick G. Durand prg.provider=Plealog # Place of the source code prg.url=https://github.com/pgdurand From e25567fea2152b71558adfca4d914a9cc0e900dc Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:21:41 +0100 Subject: [PATCH 02/10] ensure bash 5 is available, export appropriately KL_ variables, simplify script --- scripts/bdm.sh | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/bdm.sh b/scripts/bdm.sh index a01eed2..0a41c91 100755 --- a/scripts/bdm.sh +++ b/scripts/bdm.sh @@ -2,7 +2,7 @@ # # ------------------------------------------------------------------- # BeeDeeM starter command for macOS/Linux -# Copyright (c) - Patrick G. Durand, 2007-2022 +# Copyright (c) - Patrick G. Durand, 2007-2023 # ------------------------------------------------------------------- # User manual: # https://pgdurand.gitbooks.io/beedeem/ @@ -22,13 +22,20 @@ # conf directory. If not set, use ${user.dir}/conf. # -DKL_LOG_TYPE=none|console|file(default) # -# KL_WORKING_DIR, KL_CONF_DIR and KL_LOG_FILE can be defined using -# env variables before calling this script. Additional JRE arguments +# Alternatively, you can set these special variables as environment +# variables BEFORE calling this script. Additional JRE arguments # can also be passed in to this script using env variable KL_JRE_ARGS. # # Proxy configuration: update configuration file: # ${beedeemHome}/conf/system/network.config. +# *** Bank installation scripts of BeeDeeM (conf/scripts) requires BASH 5 +BASH_VER=$(bash --version | grep ", version" | cut -d' ' -f4 | cut -d'.' -f1) +if [ "$BASH_VER" -lt "5" ]; then + echo "/!\ ERROR: BeeDeeM requires BASH release 5 (yours is: $BASH_VER)" + exit 1 +fi + # *** Application home KL_APP_HOME=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) # For Conda installation, scripts are in the bin directory, so get correct home @@ -36,26 +43,21 @@ KL_APP_HOME=$( cd -P -- "$(dirname -- "$(command -v -- "$0")")" && pwd -P ) # *** Working directory if [ ! "$KL_WORKING_DIR" ]; then - KL_WORKING_DIR=@KL_WORKING_DIR@ + export KL_WORKING_DIR=@KL_WORKING_DIR@ fi # *** Configuration directory if [ ! "$KL_CONF_DIR" ]; then - KL_CONF_DIR=$KL_APP_HOME/conf + export KL_CONF_DIR=$KL_APP_HOME/conf fi -# *** Optional JRE arguments +# *** Optional JRE arguments (at least RAM specs) if [ ! "$KL_JRE_ARGS" ]; then KL_JRE_ARGS="@JAVA_ARGS@" fi # *** Java VM -KL_JAVA_ARGS="$KL_JRE_ARGS -DKL_HOME=$KL_APP_HOME -DKL_WORKING_DIR=$KL_WORKING_DIR -DKL_CONF_DIR=$KL_CONF_DIR" - -# *** Optional redefinition of log file -if [ ! -z "$KL_LOG_FILE" ]; then - KL_JAVA_ARGS+=" -DKL_LOG_FILE=$KL_LOG_FILE" -fi +KL_JAVA_ARGS="$KL_JRE_ARGS -DKL_HOME=$KL_APP_HOME" # *** JARs section KL_JAR_LIST_TMP=`\ls $KL_APP_HOME/bin/*.jar` From 444a203b807bf0680e14918e18b6b52d013e0c1e Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:22:07 +0100 Subject: [PATCH 03/10] simplify by removing JAVA_PATH --- scripts/envDBMS | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scripts/envDBMS b/scripts/envDBMS index b3b1ad8..1d5f5a3 100755 --- a/scripts/envDBMS +++ b/scripts/envDBMS @@ -1,19 +1,12 @@ -# Provide here the path to the home directory of an -# Oracle JRE release 1.7 or above -# -JAVA_HOME=/usr/local/jre1.7 [update as needed] - -export JAVA_HOME - # Provide here the path to the home directory of a Ant -# 1.6.5 or above. By default, DBMS installer will use its +# 1.9 or above. By default, DBMS installer will use its # own Ant. # ANT_HOME=$PWD/ant export ANT_HOME -PATH=$JAVA_HOME/bin:$ANT_HOME/bin:$PATH +PATH=$ANT_HOME/bin:$PATH export PATH From 32a7bb86a4c1737f7f7effcfda14022b83e0b0d1 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:22:52 +0100 Subject: [PATCH 04/10] update script to use bdm command only, deploy test_bdm for legacy installer --- scripts/deploy-std.xml | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/scripts/deploy-std.xml b/scripts/deploy-std.xml index 38afe5d..d787c4a 100755 --- a/scripts/deploy-std.xml +++ b/scripts/deploy-std.xml @@ -34,7 +34,6 @@ Installation dir: ${installDir} Working dir : ${workingDir} BioBase dir : ${biobaseRootDir} - JVM dir : ${javaDir} Java args : ${javaArgs} If ok, press Return key to start the installation... @@ -46,7 +45,6 @@ - @@ -68,24 +66,26 @@ - + - - - - - - - + + + + + + + + + - + - + @@ -108,7 +108,7 @@ - + @@ -128,13 +128,18 @@ - + + + + + + @@ -143,7 +148,7 @@ - + @@ -160,7 +165,7 @@ - + From 434fad4c854a04c8379e5102041e1c0b581ed5f8 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:23:12 +0100 Subject: [PATCH 05/10] fix toolName --- src/bzh/plealog/dbmirror/main/DumpBankList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bzh/plealog/dbmirror/main/DumpBankList.java b/src/bzh/plealog/dbmirror/main/DumpBankList.java index 8b8d20d..32f6430 100644 --- a/src/bzh/plealog/dbmirror/main/DumpBankList.java +++ b/src/bzh/plealog/dbmirror/main/DumpBankList.java @@ -321,7 +321,7 @@ public boolean execute(String[] args) { CommandLine cmdLine; OutputStream os=System.out; - String toolName = DBMSMessages.getString("Tool.Query.name"); + String toolName = DBMSMessages.getString("Tool.Dump.name"); // Configure software StarterUtils.configureApplication(null, toolName, true, false, true); From a3556c168f4af39cfb541ef3f11f7a1d85e46e2e Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:26:57 +0100 Subject: [PATCH 06/10] fix messages --- src/bzh/plealog/dbmirror/ui/resources/messages.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bzh/plealog/dbmirror/ui/resources/messages.properties b/src/bzh/plealog/dbmirror/ui/resources/messages.properties index bad24c8..e7b11ee 100755 --- a/src/bzh/plealog/dbmirror/ui/resources/messages.properties +++ b/src/bzh/plealog/dbmirror/ui/resources/messages.properties @@ -314,6 +314,8 @@ Tool.Master.intro=commands are\: Tool.Master.more=For more details on use, type: bdm -h Tool.Master.err.cmd=Command unknown\: Tool.Master.err2.cmd=Unable to execute command\: +Tool.Master.err3.cmd=Error while scanning for BeeDeeM main classes\: +Tool.Master.err4.cmd=Class not found\: Tool.DeleteBank.name=DeleteBank Tool.DeleteBank.arg1.lbl=bank-code From 0907bb82b8ea9a6df0dba8873469c21f8e167fa8 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:27:25 +0100 Subject: [PATCH 07/10] add auto-test for legacy installer --- scripts/test_bdm.sh | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 scripts/test_bdm.sh diff --git a/scripts/test_bdm.sh b/scripts/test_bdm.sh new file mode 100755 index 0000000..4fd2dba --- /dev/null +++ b/scripts/test_bdm.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +# ================================================= +# Test script for BeeDeeM legacy installer. +# To be used directly on the command-line. +# DO NOT use for Singularity/Docker installations. +# +# How to? +# +# Step 1: install BeeDeeM as stated at: +# https://pgdurand.gitbook.io/beedeem/installation/installation#legacy-installation +# +# Step 2: run test as follows: +# ./test_bdm.sh +# -- +# P. Durand (SeBiMER, Ifremer), last updated on Jan 2023 +# ================================================= + +# == TEST 1 =========================================== +echo "*** TEST 1: Start simple bank installation" +# set log mode to console (otherwise default is a log file located in KL_WORKING_DIR +export KL_LOG_TYPE=console +# Set the bank to install +# This is a '.dsc' file located in BeeDeeM installation path at ${beedeem-home}/conf/descriptors +DESCRIPTOR="SwissProt_human" +# start BeeDeeM with 'install' command +./bdm install -desc ${DESCRIPTOR} +# check whether command succeeded or not +if [ $? -eq 0 ]; then + echo "TEST 1: SUCCESS" +else + echo "TEST 1: FAILED." + exit 1 +fi + +# == TEST 2 =========================================== +echo "*** TEST 2: list installed bank" +# reset log mode to file +export KL_LOG_TYPE=file +# start BeeDeeM with 'info' command +./bdm info -d all -f txt +if [ $? -eq 0 ]; then + echo "TEST 2: SUCCESS" +else + echo "TEST 2: FAILED. Review log file in: @KL_WORKING_DIR@" + exit 1 +fi + +# == TEST 3 =========================================== +# Change default log file name to something else +export KL_LOG_FILE=query.log +SW_ENTRY="ZZZ3_HUMAN" +echo "*** TEST 3: query bank for entry: $SW_ENTRY" +# start BeeDeeM with 'query' command +./bdm query -d protein -f txt -i $SW_ENTRY +if [ $? -eq 0 ]; then + echo "TEST 3: SUCCESS" +else + echo "TEST 3: FAILED. Review log file: @KL_WORKING_DIR@" + exit 1 +fi + From 9fd9c4d9ead074e210a31451451f973ba5b8b7d8 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:27:55 +0100 Subject: [PATCH 08/10] add auto-test for legacy installer --- build.xml | 66 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/build.xml b/build.xml index b28522a..8f9abe0 100644 --- a/build.xml +++ b/build.xml @@ -217,76 +217,17 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -316,7 +257,6 @@ - From 3fd671686f8954f02cf72c5bf24cf26ea238110b Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:31:13 +0100 Subject: [PATCH 09/10] add reflective API capable of scanning dir and jar files --- NOTICE.txt | 5 +- src/bzh/plealog/dbmirror/main/BeeDeeMain.java | 60 +- .../annotation/AnnotationDetector.java | 690 ++++++++++++++++++ .../infomas/annotation/ClassFileBuffer.java | 240 ++++++ .../infomas/annotation/ClassFileIterator.java | 146 ++++ src/eu/infomas/annotation/FileIterator.java | 122 ++++ src/eu/infomas/annotation/README.txt | 7 + .../infomas/annotation/ResourceIterator.java | 43 ++ .../infomas/annotation/ZipFileIterator.java | 99 +++ src/eu/infomas/annotation/package-info.java | 29 + 10 files changed, 1418 insertions(+), 23 deletions(-) create mode 100644 src/eu/infomas/annotation/AnnotationDetector.java create mode 100644 src/eu/infomas/annotation/ClassFileBuffer.java create mode 100644 src/eu/infomas/annotation/ClassFileIterator.java create mode 100644 src/eu/infomas/annotation/FileIterator.java create mode 100644 src/eu/infomas/annotation/README.txt create mode 100644 src/eu/infomas/annotation/ResourceIterator.java create mode 100644 src/eu/infomas/annotation/ZipFileIterator.java create mode 100644 src/eu/infomas/annotation/package-info.java diff --git a/NOTICE.txt b/NOTICE.txt index 27c361e..fdaca5d 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,4 @@ -This product includes software developed by: +This software includes software developed by: The Plealog Team (http://www.plealog.com) Plealog license 1 is here: http://www.apache.org/licenses/LICENSE-2.0 @@ -34,3 +34,6 @@ This product includes software developed by: Biojava license is here: http://www.gnu.org/copyleft/lesser.html It targets: biojava, bytecode + The Informas Annotation framework + License is here: https://github.com/rmuller/infomas-asl + It targets: eu.informas.annotation package \ No newline at end of file diff --git a/src/bzh/plealog/dbmirror/main/BeeDeeMain.java b/src/bzh/plealog/dbmirror/main/BeeDeeMain.java index 2a357d0..7fe9492 100644 --- a/src/bzh/plealog/dbmirror/main/BeeDeeMain.java +++ b/src/bzh/plealog/dbmirror/main/BeeDeeMain.java @@ -16,19 +16,20 @@ */ package bzh.plealog.dbmirror.main; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.IOException; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Properties; import java.util.Set; -import java.util.stream.Collectors; import bzh.plealog.dbmirror.ui.resources.DBMSMessages; +import eu.infomas.annotation.AnnotationDetector; +import eu.infomas.annotation.AnnotationDetector.TypeReporter; /** * This class starts BeeDeeM for all commands. @@ -45,23 +46,38 @@ */ public class BeeDeeMain { - /** Code from: https://www.baeldung.com/java-find-all-classes-in-package*/ - public static Set> findAllClassesUsingClassLoader( - String packageName) { - InputStream stream = ClassLoader.getSystemClassLoader() - .getResourceAsStream(packageName.replaceAll("[.]", "/")); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - return reader.lines().filter(line -> line.endsWith(".class")) - .map(line -> getClass(line, packageName)).collect(Collectors.toSet()); + + /** Code from: https://github.com/rmuller/infomas-asl + * Adapted in package eu.informas.annotation available in BeeDeeM; see README.txt, there. + **/ + public static Set> findAllClassesUsingClassLoader(){ + HashSet> classes = new HashSet<>(); + final TypeReporter reporter = new TypeReporter() { + @SuppressWarnings("unchecked") + @Override + public Class[] annotations() { + return new Class[]{BdmTool.class}; + } + @Override + public void reportTypeAnnotation(Class annotation, + String className) { + try { + classes.add(Class.forName(className)); + } catch (ClassNotFoundException e) { + System.err.println(DBMSMessages.getString("Tool.Master.err4.cmd")+" "+e.getMessage()); + System.exit(1);//serious error, should not happen! + } + } + + }; + final AnnotationDetector cf = new AnnotationDetector(reporter); + try { + cf.detect(); + } catch (IOException e) { + System.err.println(DBMSMessages.getString("Tool.Master.err3.cmd")+" "+e.getMessage()); + System.exit(1);//serious error, should not happen! } - private static Class getClass(String className, String packageName) { - try { - return Class.forName(packageName + "." - + className.substring(0, className.lastIndexOf('.'))); - } catch (ClassNotFoundException e) { - System.err.println(e); - } - return null; + return classes; } /** */ @@ -71,7 +87,7 @@ private static void dumpHelp() { System.out.print(" "); System.out.println(DBMSMessages.getString("Tool.Master.intro")); Hashtable tools = new Hashtable(); - Set> clazz = findAllClassesUsingClassLoader(new BeeDeeMain().getClass().getPackage().getName()); + Set> clazz = findAllClassesUsingClassLoader(); for(Class c : clazz) { BdmTool bdmT = c.getAnnotation(BdmTool.class); if (bdmT != null) { @@ -133,7 +149,7 @@ public static void main(String[] args) { // Get all classes from current package // to locate ones being annotated as BdmTool classes boolean cmdOk = false; - Set> clazz = findAllClassesUsingClassLoader(new BeeDeeMain().getClass().getPackage().getName()); + Set> clazz = findAllClassesUsingClassLoader(); for(Class c : clazz) { BdmTool bdmT = c.getAnnotation(BdmTool.class); String bdmTCmd = bdmT != null ? bdmT.command() : "-" ; diff --git a/src/eu/infomas/annotation/AnnotationDetector.java b/src/eu/infomas/annotation/AnnotationDetector.java new file mode 100644 index 0000000..8488e0b --- /dev/null +++ b/src/eu/infomas/annotation/AnnotationDetector.java @@ -0,0 +1,690 @@ +/* AnnotationDetector.java + * + * Created: 2011-10-10 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2011 - 2016 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.DataInput; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * {@code AnnotationDetector} reads Java Class File (".class") files and reports the + * encountered annotations via a simple, developer friendly API. + *

+ * A Java Class File consists of a stream of 8-bit bytes. All 16-bit, 32-bit, and 64-bit + * quantities are constructed by reading in two, four, and eight consecutive 8-bit + * bytes, respectively. Multi byte data items are always stored in big-endian order, + * where the high bytes come first. In the Java and Java 2 platforms, this format is + * supported by interfaces {@link java.io.DataInput} and {@link java.io.DataOutput}. + *

+ * A class file consists of a single ClassFile structure: + *

+ * ClassFile {
+ *   u4 magic;
+ *   u2 minor_version;
+ *   u2 major_version;
+ *   u2 constant_pool_count;
+ *   cp_info constant_pool[constant_pool_count-1];
+ *   u2 access_flags;
+ *   u2 this_class;
+ *   u2 super_class;
+ *   u2 interfaces_count;
+ *   u2 interfaces[interfaces_count];
+ *   u2 fields_count;
+ *   field_info fields[fields_count];
+ *   u2 methods_count;
+ *   method_info methods[methods_count];
+ *   u2 attributes_count;
+ *   attribute_info attributes[attributes_count];
+ * }
+ *
+ * Where:
+ * u1 unsigned byte {@link java.io.DataInput#readUnsignedByte()}
+ * u2 unsigned short {@link java.io.DataInput#readUnsignedShort()}
+ * u4 unsigned int {@link java.io.DataInput#readInt()}
+ *
+ * Annotations are stored as Attributes (i.e. "RuntimeVisibleAnnotations" and
+ * "RuntimeInvisibleAnnotations").
+ * 
+ * References: + * + *

+ * Similar projects / libraries: + *

+ *

+ * All above mentioned projects make use of a byte code manipulation library (like BCEL, + * ASM or Javassist). + * + * @author Ronald K. Muller + * @since annotation-detector 3.0.0 + */ +public final class AnnotationDetector { + + /** + * {@code Reporter} is the base interface, used to report the detected annotations. + * Every category of annotations (i.e. Type, Field and Method) has its own specialized + * interface. This enables an efficient way of reporting the detected annotations. + */ + public interface Reporter { + + /** + * Return the {@code Annotation} classes which must be reported (all other + * annotations are skipped). + */ + Class[] annotations(); + + } + + /** + * A {@code Reporter} for type annotations. + */ + public interface TypeReporter extends Reporter { + + /** + * This call back method is used to report an type level {@code Annotation}. + * Only {@code Annotation}s, specified by {@link #annotations()} are reported! + */ + void reportTypeAnnotation(Class annotation, String className); + + } + + /** + * A {@code Reporter} for field annotations. + */ + public interface FieldReporter extends Reporter { + + /** + * This call back method is used to report an field level {@code Annotation}. + * Only {@code Annotation}s, specified by {@link #annotations()} are reported! + */ + void reportFieldAnnotation(Class annotation, String className, + String fieldName); + + } + + /** + * A {@code Reporter} for method annotations. + */ + public interface MethodReporter extends Reporter { + + /** + * This call back method is used to report an method level {@code Annotation}. + * Only {@code Annotation}s, specified by {@link #annotations()} are reported! + */ + void reportMethodAnnotation(Class annotation, String className, + String methodName); + + } + + // Only used during development. If set to "true" debug messages are displayed. + private static final boolean DEBUG = false; + + // Constant Pool type tags + private static final int CP_UTF8 = 1; + private static final int CP_INTEGER = 3; + private static final int CP_FLOAT = 4; + private static final int CP_LONG = 5; + private static final int CP_DOUBLE = 6; + private static final int CP_CLASS = 7; + private static final int CP_STRING = 8; + private static final int CP_REF_FIELD = 9; + private static final int CP_REF_METHOD = 10; + private static final int CP_REF_INTERFACE = 11; + private static final int CP_NAME_AND_TYPE = 12; + private static final int CP_METHOD_HANDLE = 15; + private static final int CP_METHOD_TYPE = 16; + private static final int CP_INVOKE_DYNAMIC = 18; + + // AnnotationElementValue + private static final int BYTE = 'B'; + private static final int CHAR = 'C'; + private static final int DOUBLE = 'D'; + private static final int FLOAT = 'F'; + private static final int INT = 'I'; + private static final int LONG = 'J'; + private static final int SHORT = 'S'; + private static final int BOOLEAN = 'Z'; + // used for AnnotationElement only + private static final int STRING = 's'; + private static final int ENUM = 'e'; + private static final int CLASS = 'c'; + private static final int ANNOTATION = '@'; + private static final int ARRAY = '['; + + // The buffer is reused during the life cycle of this AnnotationDetector instance + private final ClassFileBuffer cpBuffer = new ClassFileBuffer(); + // the annotation types to report, see {@link #annotations()} + private final Map> annotations; + + private TypeReporter typeReporter; + private FieldReporter fieldReporter; + private MethodReporter methodReporter; + + // the 'raw' name of this interface or class (using '/' instead of '.' in package name) + private String typeName; + // Reusing the constantPool is not needed for better performance + private Object[] constantPool; + private String memberName; + + /** + * Create a new {@code AnnotationDetector}, reporting the detected annotations + * to the specified {@code Reporter}. + */ + public AnnotationDetector(final Reporter reporter) { + final Class[] a = reporter.annotations(); + annotations = new HashMap>(a.length); + // map "raw" type names to Class object + for (int i = 0; i < a.length; ++i) { + annotations.put("L" + a[i].getName().replace('.', '/') + ";", a[i]); + } + if (reporter instanceof TypeReporter) { + typeReporter = (TypeReporter)reporter; + } + if (reporter instanceof FieldReporter) { + fieldReporter = (FieldReporter)reporter; + } + if (reporter instanceof MethodReporter) { + methodReporter = (MethodReporter)reporter; + } + if (typeReporter == null && fieldReporter == null && methodReporter == null) { + throw new AssertionError("No reporter defined"); + } + } + + /** + * Report all Java ClassFile files available on the class path. + * + * @see #detect(File...) + */ + public void detect() throws IOException { + detect(new ClassFileIterator()); + } + + /** + * Report all Java ClassFile files available on the class path within + * the specified packages and sub packages. + * + * @see #detect(File...) + */ + public void detect(final String... packageNames) throws IOException { + final String[] pkgNameFilter = new String[packageNames.length]; + for (int i = 0; i < pkgNameFilter.length; ++i) { + pkgNameFilter[i] = packageNames[i].replace('.', '/'); + if (!pkgNameFilter[i].endsWith("/")) { + pkgNameFilter[i] = pkgNameFilter[i].concat("/"); + } + } + final Set files = new HashSet(); + final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + for (final String packageName : pkgNameFilter) { + final Enumeration resourceEnum = loader.getResources(packageName); + while (resourceEnum.hasMoreElements()) { + final URL url = resourceEnum.nextElement(); + if ("file".equals(url.getProtocol())) { + final File dir = toFile(url); + if (dir.isDirectory()) { + files.add(dir); + } else { + throw new AssertionError("Not a recognized file URL: " + url); + } + } /*else if (url.getProtocol().startsWith("vfs")) {//Requires JBoss stuff, not used in BeeDeeM + detect(new VfsResourceIterator(url)); + } */else { + final File jarFile = toFile(openJarURLConnection(url).getJarFileURL()); + if (jarFile.isFile()) { + files.add(jarFile); + } else { + throw new AssertionError("Not a File: " + jarFile); + } + } + } + } + if (DEBUG) { + print("Files to scan: %s", files); + } + if (!files.isEmpty()) { + // see http://shipilev.net/blog/2016/arrays-wisdom-ancients/#_conclusion + detect(new ClassFileIterator(files.toArray(new File[0]), pkgNameFilter)); + } + } + + /** + * Scan all Java ClassFile ({@code *.class}) files available in the specified files + * and/or directories. + *

+ * In Java, the + * + * Class path contains directories (top level directory as package root) and/or + * jar files (including zip files). + *

+ * Note that non-class files (files, not starting with the magic number + * {@code CAFEBABE} are silently ignored. + * + * @param filesOrDirectories Valid files are: jar files, Java *.class files (all other + * files are silently ignored) and directories which are package root directories + */ + public void detect(final File... filesOrDirectories) throws IOException { + if (DEBUG) { + print("detectFilesOrDirectories: %s", (Object)filesOrDirectories); + } + detect(new ClassFileIterator(filesOrDirectories, null)); + } + + // private + + private File toFile(final URL url) { + // only correct way to convert the URL to a File object, also see issue #16 + // Do not use URLDecoder + try { + return new File(url.toURI()); + } catch (URISyntaxException ex) { + // we do not expect an URISyntaxException here + throw new AssertionError("Unable to convert URI to File: " + url); + } + } + + private JarURLConnection openJarURLConnection(final URL url) throws IOException { + final URL checkedUrl; + if ("zip".equals(url.getProtocol())) { + // WebLogic returns URL with "zip" protocol, returning a + // weblogic.utils.zip.ZipURLConnection when opened + // Easy fix is to convert this URL to jar URL + checkedUrl = new URL(url.toExternalForm().replace("zip:/", "jar:file:/")); + } else { + checkedUrl = url; + } + URLConnection urlConnection = checkedUrl.openConnection(); + // GlassFish 4.1.1 is providing a URLConnection of type: + // http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/ + // apache/felix/framework/URLHandlersBundleURLConnection.java?view=markup + // Which does _not_ extend JarURLConnection. + // This bit of reflection allows us to call the getLocalURL method which + // actually returns a URL to a jar file. + if (checkedUrl.getProtocol().startsWith("bundle")) { + try { + final Method m = urlConnection.getClass().getDeclaredMethod("getLocalURL"); + if (!m.isAccessible()) { + m.setAccessible(true); + } + final URL jarUrl = (URL)m.invoke(urlConnection); + urlConnection = jarUrl.openConnection(); + } catch (Exception ex) { + throw new AssertionError("Couldn't read jar file URL from bundle: " + ex); + } + } + if (urlConnection instanceof JarURLConnection) { + return (JarURLConnection)urlConnection; + } else { + throw new AssertionError( + "Unknown URLConnection type: " + urlConnection.getClass().getName()); + } + } + + public void detect(final ResourceIterator iterator) throws IOException { + InputStream stream; + while ((stream = iterator.next()) != null) { + try { + cpBuffer.readFrom(stream); + if (hasCafebabe(cpBuffer)) { + detect(cpBuffer); + } // else ignore + } catch (Throwable t) { + // catch all errors + if (!(stream instanceof FileInputStream)) { + // in case of an error we close the ZIP File here + stream.close(); + } + } finally { + // closing InputStream from ZIP Entry is handled by ZipFileIterator + if (stream instanceof FileInputStream) { + stream.close(); + } + } + } + } + + private boolean hasCafebabe(final ClassFileBuffer buffer) throws IOException { + return buffer.size() > 4 && buffer.readInt() == 0xCAFEBABE; + } + + /** + * Inspect the given (Java) class file in streaming mode. + */ + private void detect(final DataInput di) throws IOException { + readVersion(di); + readConstantPoolEntries(di); + readAccessFlags(di); + readThisClass(di); + readSuperClass(di); + readInterfaces(di); + readFields(di); + readMethods(di); + readAttributes(di, 'T', typeReporter == null); + } + + private void readVersion(final DataInput di) throws IOException { + // sequence: minor version, major version (argument_index is 1-based) + if (DEBUG) { + print("Java Class version %2$d.%1$d", + di.readUnsignedShort(), di.readUnsignedShort()); + } else { + di.skipBytes(4); + } + } + + private void readConstantPoolEntries(final DataInput di) throws IOException { + final int count = di.readUnsignedShort(); + constantPool = new Object[count]; + for (int i = 1; i < count; ++i) { + if (readConstantPoolEntry(di, i)) { + // double slot + ++i; + } + } + } + + /** + * Return {@code true} if a double slot is read (in case of Double or Long constant). + */ + private boolean readConstantPoolEntry(final DataInput di, final int index) + throws IOException { + + final int tag = di.readUnsignedByte(); + switch (tag) { + case CP_METHOD_TYPE: + di.skipBytes(2); // readUnsignedShort() + return false; + case CP_METHOD_HANDLE: + di.skipBytes(3); + return false; + case CP_INTEGER: + case CP_FLOAT: + case CP_REF_FIELD: + case CP_REF_METHOD: + case CP_REF_INTERFACE: + case CP_NAME_AND_TYPE: + case CP_INVOKE_DYNAMIC: + di.skipBytes(4); // readInt() / readFloat() / readUnsignedShort() * 2 + return false; + case CP_LONG: + case CP_DOUBLE: + di.skipBytes(8); // readLong() / readDouble() + return true; + case CP_UTF8: + constantPool[index] = di.readUTF(); + return false; + case CP_CLASS: + case CP_STRING: + // reference to CP_UTF8 entry. The referenced index can have a higher number! + constantPool[index] = di.readUnsignedShort(); + return false; + default: + throw new ClassFormatError( + "Unkown tag value for constant pool entry: " + tag); + } + } + + private void readAccessFlags(final DataInput di) throws IOException { + di.skipBytes(2); // u2 + } + + private void readThisClass(final DataInput di) throws IOException { + typeName = resolveUtf8(di); + if (DEBUG) { + print("read type '%s'", typeName); + } + } + + private void readSuperClass(final DataInput di) throws IOException { + di.skipBytes(2); // u2 + } + + private void readInterfaces(final DataInput di) throws IOException { + final int count = di.readUnsignedShort(); + di.skipBytes(count * 2); // count * u2 + } + + private void readFields(final DataInput di) throws IOException { + final int count = di.readUnsignedShort(); + if (DEBUG) { + print("field count = %d", count); + } + for (int i = 0; i < count; ++i) { + readAccessFlags(di); + memberName = resolveUtf8(di); + final String descriptor = resolveUtf8(di); + readAttributes(di, 'F', fieldReporter == null); + if (DEBUG) { + print("Field: %s, descriptor: %s", memberName, descriptor); + } + } + } + + private void readMethods(final DataInput di) throws IOException { + final int count = di.readUnsignedShort(); + if (DEBUG) { + print("method count = %d", count); + } + for (int i = 0; i < count; ++i) { + readAccessFlags(di); + memberName = resolveUtf8(di); + final String descriptor = resolveUtf8(di); + readAttributes(di, 'M', methodReporter == null); + if (DEBUG) { + print("Method: %s, descriptor: %s", memberName, descriptor); + } + } + } + + private void readAttributes(final DataInput di, final char reporterType, + final boolean skipReporting) throws IOException { + + final int count = di.readUnsignedShort(); + if (DEBUG) { + print("attribute count (%s) = %d", reporterType, count); + } + for (int i = 0; i < count; ++i) { + final String name = resolveUtf8(di); + // in bytes, use this to skip the attribute info block + final int length = di.readInt(); + if (!skipReporting && + ("RuntimeVisibleAnnotations".equals(name) || + "RuntimeInvisibleAnnotations".equals(name))) { + readAnnotations(di, reporterType); + } else { + if (DEBUG) { + print("skip attribute %s", name); + } + di.skipBytes(length); + } + } + } + + private void readAnnotations(final DataInput di, final char reporterType) + throws IOException { + + // the number of Runtime(In)VisibleAnnotations + final int count = di.readUnsignedShort(); + if (DEBUG) { + print("annotation count (%s) = %d", reporterType, count); + } + for (int i = 0; i < count; ++i) { + final String rawTypeName = readAnnotation(di); + final Class type = annotations.get(rawTypeName); + if (type == null) { + continue; + } + final String externalTypeName = typeName.replace('/', '.'); + switch (reporterType) { + case 'T': + typeReporter.reportTypeAnnotation(type, externalTypeName); + break; + case 'F': + fieldReporter.reportFieldAnnotation(type, externalTypeName, memberName); + break; + case 'M': + methodReporter.reportMethodAnnotation(type, externalTypeName, memberName); + break; + default: + throw new AssertionError("reporterType=" + reporterType); + } + } + } + + private String readAnnotation(final DataInput di) throws IOException { + final String rawTypeName = resolveUtf8(di); + // num_element_value_pairs + final int count = di.readUnsignedShort(); + if (DEBUG) { + print("annotation elements count: %d", count); + } + for (int i = 0; i < count; ++i) { + if (DEBUG) { + print("element '%s'", resolveUtf8(di)); + } else { + di.skipBytes(2); + } + readAnnotationElementValue(di); + } + return rawTypeName; + } + + + private void readAnnotationElementValue(final DataInput di) throws IOException { + final int tag = di.readUnsignedByte(); + if (DEBUG) { + print("tag='%c'", (char)tag); + } + switch (tag) { + case BYTE: + case CHAR: + case DOUBLE: + case FLOAT: + case INT: + case LONG: + case SHORT: + case BOOLEAN: + case STRING: + di.skipBytes(2); + break; + case ENUM: + di.skipBytes(4); // 2 * u2 + break; + case CLASS: + di.skipBytes(2); + break; + case ANNOTATION: + readAnnotation(di); + break; + case ARRAY: + final int count = di.readUnsignedShort(); + for (int i = 0; i < count; ++i) { + readAnnotationElementValue(di); + } + break; + default: + throw new ClassFormatError("Not a valid annotation element type tag: 0x" + + Integer.toHexString(tag)); + } + } + + /** + * Look up the String value, identified by the u2 index value from constant pool + * (direct or indirect). + */ + private String resolveUtf8(final DataInput di) throws IOException { + final int index = di.readUnsignedShort(); + final Object value = constantPool[index]; + final String s; + if (value instanceof Integer) { + s = (String)constantPool[(Integer)value]; + if (DEBUG) { + print("resolveUtf8(%d): %d --> %s", index, value, s); + } + } else { + s = (String)value; + if (DEBUG) { + print("resolveUtf8(%d): %s", index, s); + } + } + + return s; + } + + /** + * Helper method for simple (debug) logging. + */ + private static void print(final String message, final Object... args) { + if (DEBUG) { + final String logMessage; + if (args.length == 0) { + logMessage = message; + } else { + for (int i = 0; i < args.length; ++i) { + // arguments may be null + if (args[i] == null) { + continue; + } + if (args[i].getClass().isArray()) { + // cast back to array! Note that primitive arrays are not supported + args[i] = Arrays.toString((Object[])args[i]); + } else if (args[i] == Class.class) { + args[i] = ((Class)args[i]).getName(); + } + } + logMessage = String.format(message, args); + } + System.out.println(logMessage); + } + } + +} diff --git a/src/eu/infomas/annotation/ClassFileBuffer.java b/src/eu/infomas/annotation/ClassFileBuffer.java new file mode 100644 index 0000000..f6020a8 --- /dev/null +++ b/src/eu/infomas/annotation/ClassFileBuffer.java @@ -0,0 +1,240 @@ +/* ClassFileBuffer.java + * + * Created: 2011-10-10 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * {@code ClassFileBuffer} is used by {@link AnnotationDetector} to efficiently read Java + * ClassFile files from an {@link InputStream} and parse the content via the {@link DataInput} + * interface. + *

+ * Note that Java ClassFile files can grow really big, + * {@code com.sun.corba.se.impl.logging.ORBUtilSystemException} is 128.2 kb! + * + * @author Ronald K. Muller + * @since annotation-detector 3.0.0 + */ +final class ClassFileBuffer implements DataInput { + + private byte[] buffer; + private int size; // the number of significant bytes read + private int pointer; // the "read pointer" + + /** + * Create a new, empty {@code ClassFileBuffer} with the default initial capacity (8 kb). + */ + ClassFileBuffer() { + this(8 * 1024); + } + + /** + * Create a new, empty {@code ClassFileBuffer} with the specified initial capacity. + * The initial capacity must be greater than zero. The internal buffer will grow + * automatically when a higher capacity is required. However, buffer resizing occurs + * extra overhead. So in good initial capacity is important in performance critical + * situations. + */ + ClassFileBuffer(final int initialCapacity) { + if (initialCapacity < 1) { + throw new IllegalArgumentException("initialCapacity < 1: " + initialCapacity); + } + this.buffer = new byte[initialCapacity]; + } + + /** + * Clear and fill the buffer of this {@code ClassFileBuffer} with the + * supplied byte stream. + * The read pointer is reset to the start of the byte array. + */ + public void readFrom(final InputStream in) throws IOException { + pointer = 0; + size = 0; + int n; + do { + n = in.read(buffer, size, buffer.length - size); + if (n > 0) { + size += n; + } + resizeIfNeeded(); + } while (n >= 0); + } + + /** + * Sets the file-pointer offset, measured from the beginning of this file, + * at which the next read or write occurs. + */ + public void seek(final int position) throws IOException { + if (position < 0) { + throw new IllegalArgumentException("position < 0: " + position); + } + if (position > size) { + throw new EOFException(); + } + this.pointer = position; + } + + /** + * Return the size (in bytes) of this Java ClassFile file. + */ + public int size() { + return size; + } + + // DataInput + + @Override + public void readFully(final byte[] bytes) throws IOException { + readFully(bytes, 0, bytes.length); + } + + @Override + public void readFully(final byte[] bytes, final int offset, final int length) + throws IOException { + + if (length < 0 || offset < 0 || offset + length > bytes.length) { + throw new IndexOutOfBoundsException(); + } + if (pointer + length > size) { + throw new EOFException(); + } + System.arraycopy(buffer, pointer, bytes, offset, length); + pointer += length; + } + + @Override + public int skipBytes(final int n) throws IOException { + seek(pointer + n); + return n; + } + + @Override + public byte readByte() throws IOException { + if (pointer >= size) { + throw new EOFException(); + } + return buffer[pointer++]; + } + + @Override + public boolean readBoolean() throws IOException { + return readByte() != 0; + } + + @Override + public int readUnsignedByte() throws IOException { + if (pointer >= size) { + throw new EOFException(); + } + return read(); + } + + @Override + public int readUnsignedShort() throws IOException { + if (pointer + 2 > size) { + throw new EOFException(); + } + return (read() << 8) + read(); + } + + @Override + public short readShort() throws IOException { + return (short)readUnsignedShort(); + } + + @Override + public char readChar() throws IOException { + return (char)readUnsignedShort(); + } + + @Override + public int readInt() throws IOException { + if (pointer + 4 > size) { + throw new EOFException(); + } + return (read() << 24) + + (read() << 16) + + (read() << 8) + + read(); + } + + @Override + public long readLong() throws IOException { + if (pointer + 8 > size) { + throw new EOFException(); + } + return ((long)read() << 56) + + ((long)read() << 48) + + ((long)read() << 40) + + ((long)read() << 32) + + (read() << 24) + + (read() << 16) + + (read() << 8) + + read(); + } + + @Override + public float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * This methods throws an {@link UnsupportedOperationException} because the method + * is deprecated and not used in the context of this implementation. + * + * @deprecated Does not support UTF-8, use readUTF() instead + */ + @Override + @Deprecated + public String readLine() throws IOException { + throw new UnsupportedOperationException("readLine() is deprecated and not supported"); + } + + @Override + public String readUTF() throws IOException { + return DataInputStream.readUTF(this); + } + + // private + + private int read() { + return buffer[pointer++] & 0xff; + } + + private void resizeIfNeeded() { + if (size >= buffer.length) { + final byte[] newBuffer = new byte[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); + buffer = newBuffer; + } + } + +} diff --git a/src/eu/infomas/annotation/ClassFileIterator.java b/src/eu/infomas/annotation/ClassFileIterator.java new file mode 100644 index 0000000..efa9936 --- /dev/null +++ b/src/eu/infomas/annotation/ClassFileIterator.java @@ -0,0 +1,146 @@ +/* ClassFileIterator.java + * + * Created: 2011-10-10 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipFile; + +/** + * {@code ClassFileIterator} is used to iterate over all Java ClassFile files available within + * a specific context. + *

+ * For every Java ClassFile ({@code .class}) an {@link InputStream} is returned. + * + * @author Ronald K. Muller + * @since annotation-detector 3.0.0 + */ +public final class ClassFileIterator extends ResourceIterator { + + private final FileIterator fileIterator; + private final String[] pkgNameFilter; + private ZipFileIterator zipIterator; + + /** + * Create a new {@code ClassFileIterator} returning all Java ClassFile files available + * from the class path ({@code System.getProperty("java.class.path")}). + */ + ClassFileIterator() { + this(classPath(), null); + } + + /** + * Create a new {@code ClassFileIterator} returning all Java ClassFile files available + * from the specified files and/or directories, including sub directories. + *

+ * If the (optional) package filter is defined, only class files staring with one of the + * defined package names are returned. + * NOTE: package names must be defined in the native format (using '/' instead of '.'). + */ + public ClassFileIterator(final File[] filesOrDirectories, final String[] pkgNameFilter) { + this.fileIterator = new FileIterator(filesOrDirectories); + this.pkgNameFilter = pkgNameFilter; + } + + /** + * Return the name of the Java ClassFile returned from the last call to {@link #next()}. + * The name is either the path name of a file or the name of an ZIP/JAR file entry. + */ + public String getName() { + // Both getPath() and getName() are very light weight method calls + return zipIterator == null ? + fileIterator.getFile().getPath() : + zipIterator.getEntry().getName(); + } + + @Override + public InputStream next() throws IOException { + while (true) { + if (zipIterator == null) { + final File file = fileIterator.next(); + // not all specified Files exists! + if (file == null || !file.isFile()) { + return null; + } else { + final String name = file.getName(); + if (name.endsWith(".class")) { + return new FileInputStream(file); + } else if (fileIterator.isRootFile() && + (endsWithIgnoreCase(name, ".jar") || isZipFile(file))) { + zipIterator = new ZipFileIterator(new ZipFile(file), pkgNameFilter); + } // else just ignore + } + } else { + final InputStream is = zipIterator.next(); + if (is == null) { + zipIterator = null; + } else { + return is; + } + } + } + } + + // private + + private boolean isZipFile(final File file) { + DataInputStream in = null; + try { + in = new DataInputStream(new FileInputStream(file)); + final int n = in.readInt(); + return n == 0x504b0304; + } catch (IOException ex) { + // silently ignore read exceptions + return false; + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException ex) { + // ignore + } + } + } + } + + /** + * Returns the class path of the current JVM instance as an array of {@link File} objects. + */ + private static File[] classPath() { + final String[] fileNames = + System.getProperty("java.class.path").split(File.pathSeparator); + final File[] files = new File[fileNames.length]; + for (int i = 0; i < files.length; ++i) { + files[i] = new File(fileNames[i]); + } + return files; + } + + private static boolean endsWithIgnoreCase(final String value, final String suffix) { + final int n = suffix.length(); + return value.regionMatches(true, value.length() - n, suffix, 0, n); + } + +} diff --git a/src/eu/infomas/annotation/FileIterator.java b/src/eu/infomas/annotation/FileIterator.java new file mode 100644 index 0000000..aa9bc22 --- /dev/null +++ b/src/eu/infomas/annotation/FileIterator.java @@ -0,0 +1,122 @@ +/* FileIterator.java + * + * Created: 2011-10-10 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.File; +import java.io.IOException; +import java.util.Deque; +import java.util.LinkedList; +import java.util.NoSuchElementException; + +/** + * {@code FileIterator} enables iteration over all files in a directory and all its sub + * directories. + *

+ * Usage: + *

+ * FileIterator iter = new FileIterator(new File("./src"));
+ * File f;
+ * while ((f = iter.next()) != null) {
+ *     // do something with f
+ *     assert f == iter.getCurrent();
+ * }
+ * 
+ * + * @author Ronald K. Muller + * @since annotation-detector 3.0.0 + */ +final class FileIterator { + + private final Deque stack = new LinkedList(); + private int rootCount; + private File current; + + /** + * Create a new {@code FileIterator} using the specified 'filesOrDirectories' as root. + *

+ * If 'filesOrDirectories' contains a file, the iterator just returns that single file. + * If 'filesOrDirectories' contains a directory, all files in that directory + * and its sub directories are returned (depth first). + * + * @param filesOrDirectories Zero or more {@link File} objects, which are iterated + * in the specified order (depth first) + */ + FileIterator(final File... filesOrDirectories) { + addReverse(filesOrDirectories); + rootCount = stack.size(); + } + + /** + * Return the last returned file or {@code null} if no more files are available. + * + * @see #next() + */ + public File getFile() { + return current; + } + + /** + * Return {@code true} if the current file is one of the files originally + * specified as one of the constructor file parameters, i.e. is a root file + * or directory. + */ + public boolean isRootFile() { + if (current == null) { + throw new NoSuchElementException(); + } + return stack.size() < rootCount; + } + + /** + * Return the next {@link File} object or {@code null} if no more files are + * available. + * + * @see #getFile() + */ + public File next() throws IOException { + if (stack.isEmpty()) { + current = null; + return null; + } else { + current = stack.removeLast(); + if (current.isDirectory()) { + if (stack.size() < rootCount) { + rootCount = stack.size(); + } + addReverse(current.listFiles()); + return next(); + } else { + return current; + } + } + } + + /** + * Add the specified files in reverse order. + */ + private void addReverse(final File[] files) { + for (int i = files.length - 1; i >= 0; --i) { + stack.add(files[i]); + } + } + +} diff --git a/src/eu/infomas/annotation/README.txt b/src/eu/infomas/annotation/README.txt new file mode 100644 index 0000000..317120d --- /dev/null +++ b/src/eu/infomas/annotation/README.txt @@ -0,0 +1,7 @@ +Code adapted from master branch of: +https://github.com/rmuller/infomas-asl + +Cannot be included as is, even with a JAR since this package includes a class requiring a JBoss dependency. +Since this JBoss stuff is not used in BeeDeeM, simply remove the class VfsResourceIterator.java + +P. Durand, Jan 2023 \ No newline at end of file diff --git a/src/eu/infomas/annotation/ResourceIterator.java b/src/eu/infomas/annotation/ResourceIterator.java new file mode 100644 index 0000000..56ddb34 --- /dev/null +++ b/src/eu/infomas/annotation/ResourceIterator.java @@ -0,0 +1,43 @@ +/* ResourceIterator.java + * + * Created: 2015-10-17 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2015 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.IOException; +import java.io.InputStream; + +/** + * {@code ResourceIterator} is an abstraction for an iterator of (Java) Class Files, provided + * as {@link InputStream}. + * + * @author Ronald K. Muller + * @since INFOMAS NG 3.0 + */ +public abstract class ResourceIterator { + + /** + * Return the next Java ClassFile as an {@code InputStream}. + *

+ * NOTICE: Client code MUST close the returned {@code InputStream}! + */ + public abstract InputStream next() throws IOException; + +} diff --git a/src/eu/infomas/annotation/ZipFileIterator.java b/src/eu/infomas/annotation/ZipFileIterator.java new file mode 100644 index 0000000..29d7051 --- /dev/null +++ b/src/eu/infomas/annotation/ZipFileIterator.java @@ -0,0 +1,99 @@ +/* ZipFileIterator.java + * + * Created: 2011-10-10 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2011 - 2013 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package eu.infomas.annotation; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * {@code ZipFileIterator} is used to iterate over all entries in a given {@code zip} or + * {@code jar} file and returning the {@link InputStream} of these entries. + *

+ * It is possible to specify an (optional) entry name filter. + *

+ * The most efficient way of iterating is used, see benchmark in test classes. + * + * @author Ronald K. Muller + * @since annotation-detector 3.0.0 + */ +final class ZipFileIterator { + + private final ZipFile zipFile; + private final String[] entryNameFilter; + private final Enumeration entries; + + private ZipEntry current; + + /** + * Create a new {@code ZipFileIterator} instance. + * + * @param zipFile The ZIP file used to iterate over all entries + * @param entryNameFilter (optional) file name filter. Only entry names starting with + * one of the specified names in the filter are returned + */ + ZipFileIterator(final ZipFile zipFile, final String[] entryNameFilter) throws IOException { + this.zipFile = zipFile; + this.entryNameFilter = entryNameFilter; + + this.entries = zipFile.entries(); + } + + public ZipEntry getEntry() { + return current; + } + + public InputStream next() throws IOException { + while (entries.hasMoreElements()) { + current = entries.nextElement(); + if (accept(current)) { + return zipFile.getInputStream(current); + } + } + // no more entries in this ZipFile, so close ZipFile + try { + // zipFile is never null here + zipFile.close(); + } catch (IOException ex) { + // suppress IOException, otherwise close() is called twice + } + return null; + } + + private boolean accept(final ZipEntry entry) { + if (entry.isDirectory()) { + return false; + } + if (entryNameFilter == null) { + return true; + } + for (final String filter : entryNameFilter) { + if (entry.getName().startsWith(filter)) { + return true; + } + } + return false; + } + +} diff --git a/src/eu/infomas/annotation/package-info.java b/src/eu/infomas/annotation/package-info.java new file mode 100644 index 0000000..46e9638 --- /dev/null +++ b/src/eu/infomas/annotation/package-info.java @@ -0,0 +1,29 @@ +/* package-info.java + * + * Created: 2013-07-09 (Year-Month-Day) + * Character encoding: UTF-8 + * + ****************************************** LICENSE ******************************************* + * + * Copyright (c) 2013 XIAM Solutions B.V. (http://www.xiam.nl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * This library can be used to scan (part of) the class path for annotated classes, + * methods or instance variables. + *

+ * For more information see + * Github site. + */ +package eu.infomas.annotation; From 8189dca35c0296f10e474d9988e426875a935641 Mon Sep 17 00:00:00 2001 From: pgdurand Date: Thu, 5 Jan 2023 19:31:53 +0100 Subject: [PATCH 10/10] fix log init to take into account new reflective API --- src/bzh/plealog/dbmirror/util/log/LoggerCentral.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bzh/plealog/dbmirror/util/log/LoggerCentral.java b/src/bzh/plealog/dbmirror/util/log/LoggerCentral.java index 60f1186..0fc2202 100755 --- a/src/bzh/plealog/dbmirror/util/log/LoggerCentral.java +++ b/src/bzh/plealog/dbmirror/util/log/LoggerCentral.java @@ -294,7 +294,7 @@ private static void initLoggers(ConfigurationBuilder builder builder.newAppenderRef(refAppender)).addAttribute("additivity", false)); builder.add(builder.newRootLogger(aLevel).add(builder.newAppenderRef(refAppender))); - Configurator.initialize(builder.build()); + Configurator.reconfigure(builder.build()); } /** @@ -336,7 +336,7 @@ private static void configureRollingFileLogger(Level aLevel, String logName) { userPath = getLogAppPath(); szLogFileName = userPath + _logAppFile; szLogFilePattern = userPath + "%d{MM-dd-yy}-" + _logAppFile; - + System.out.println("**** LogFile: "+szLogFileName); try { cleanSystemLogs(userPath, _logAppFile); } catch (Exception e) {