diff --git a/webmagic-avalon/forger b/webmagic-avalon/forger deleted file mode 160000 index 9f08a0ff..00000000 --- a/webmagic-avalon/forger +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9f08a0ffd09f5d59ae38091bca250d51aa54bfde diff --git a/webmagic-avalon/forger/LICENSE b/webmagic-avalon/forger/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/webmagic-avalon/forger/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + diff --git a/webmagic-avalon/forger/README.md b/webmagic-avalon/forger/README.md new file mode 100644 index 00000000..1e4d7f5e --- /dev/null +++ b/webmagic-avalon/forger/README.md @@ -0,0 +1,27 @@ +forger +====== + +Dynamic Java object generator with template class and configuration. + +## Compiler + +Use groovy compiler. Compile source code to Java class. + +## PropertyLoader + +Load properties of object from user input. + +## API + +```java + @Test + public void testForgerCreateByClassAnnotationCompile() throws Exception { + ForgerFactory forgerFactory = new ForgerFactory(new AnnotationPropertyLoader(), new GroovyForgerCompiler()); + Forger forger = forgerFactory.compile(Foo.SOURCE_CODE); + Fooable foo = forger.forge(ImmutableMap.of("fooa", "test")); + Field field = forger.getClazz().getDeclaredField("foo"); + field.setAccessible(true); + assertThat(field.get(foo)).isEqualTo("test"); + assertThat(foo.foo()).isEqualTo("test"); + } +``` \ No newline at end of file diff --git a/webmagic-avalon/forger/pom.xml b/webmagic-avalon/forger/pom.xml new file mode 100644 index 00000000..9738c102 --- /dev/null +++ b/webmagic-avalon/forger/pom.xml @@ -0,0 +1,193 @@ + + + + org.sonatype.oss + oss-parent + 7 + + us.codecraft + forger + 0.1.0-SNAPSHOT + 4.0.0 + jar + + UTF-8 + UTF-8 + + forger + + Dynamic Java object generator with template class and configuration. + + https://github.com/code4craft/forger/ + + + code4craft + Yihua huang + code4crafer@gmail.com + + + + scm:git:git@github.com:code4craft/forger.git + scm:git:git@github.com:code4craft/forger.git + git@github.com:code4craft/forger.git + HEAD + + + + Apache License,Version 2 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + + junit + junit + 4.11 + test + + + org.assertj + assertj-core + 1.5.0 + + + org.codehaus.groovy + groovy + 2.2.2 + + + org.slf4j + slf4j-api + 1.7.6 + + + + org.slf4j + slf4j-log4j12 + 1.7.6 + + + + org.apache.commons + commons-lang3 + 3.1 + + + + com.google.guava + guava + 15.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + UTF-8 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + false + false + true + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + UTF-8 + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + UTF-8 + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.4.1 + + + + + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.1 + + + sign-artifacts + verify + + sign + + + + + + + + + + + diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/Forger.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/Forger.java new file mode 100644 index 00000000..57ec2abf --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/Forger.java @@ -0,0 +1,36 @@ +package us.codecraft.forger; + +import us.codecraft.forger.property.Property; +import us.codecraft.forger.property.PropertyLoader; + +import java.util.List; +import java.util.Map; + +/** + * @author code4crafter@gmail.com + */ +public class Forger { + + private final Class clazz; + + private final PropertyLoader propertyLoader; + + public Forger(Class clazz,PropertyLoader propertyLoader) { + this.clazz = clazz; + this.propertyLoader = propertyLoader; + } + + public T forge(Map properties) throws IllegalAccessException, InstantiationException { + T t = clazz.newInstance(); + propertyLoader.load(t, properties); + return t; + } + + public List getPropertyNames() { + return propertyLoader.getProperties(clazz); + } + + public Class getClazz() { + return clazz; + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/ForgerFactory.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/ForgerFactory.java new file mode 100644 index 00000000..84b507b4 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/ForgerFactory.java @@ -0,0 +1,28 @@ +package us.codecraft.forger; + +import us.codecraft.forger.compiler.ForgerCompiler; +import us.codecraft.forger.property.PropertyLoader; + +/** + * @author code4crafter@gmail.com + */ +public class ForgerFactory { + + private final PropertyLoader propertyLoader; + + private final ForgerCompiler forgerCompiler; + + public ForgerFactory(PropertyLoader propertyLoader, ForgerCompiler forgerCompiler) { + this.propertyLoader = propertyLoader; + this.forgerCompiler = forgerCompiler; + } + + public Forger compile(String sourceCode) { + Class clazz = forgerCompiler.compile(sourceCode); + return new Forger(clazz, propertyLoader); + } + + public Forger create(Class clazz) { + return new Forger(clazz, propertyLoader); + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/ForgerCompiler.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/ForgerCompiler.java new file mode 100644 index 00000000..5e9e3781 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/ForgerCompiler.java @@ -0,0 +1,9 @@ +package us.codecraft.forger.compiler; + +/** + * @author code4crafter@gmail.com + */ +public interface ForgerCompiler { + + public Class compile(String sourceCode); +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/GroovyForgerCompiler.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/GroovyForgerCompiler.java new file mode 100644 index 00000000..26a137ee --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/compiler/GroovyForgerCompiler.java @@ -0,0 +1,16 @@ +package us.codecraft.forger.compiler; + +import groovy.lang.GroovyClassLoader; + +/** + * @author code4crafter@gmail.com + */ +public class GroovyForgerCompiler implements ForgerCompiler{ + + private GroovyClassLoader groovyClassLoader = new GroovyClassLoader(); + + @Override + public Class compile(String sourceCode) { + return groovyClassLoader.parseClass(sourceCode); + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AbstractPropertyLoader.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AbstractPropertyLoader.java new file mode 100644 index 00000000..f0b638d4 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AbstractPropertyLoader.java @@ -0,0 +1,112 @@ +package us.codecraft.forger.property; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import us.codecraft.forger.property.format.*; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author code4crafter@gmail.com + */ +public abstract class AbstractPropertyLoader implements PropertyLoader { + + private TypeFormatterFactory typeFormatterFactory = new TypeFormatterFactory(); + + protected Logger logger = LoggerFactory.getLogger(getClass()); + + protected TypeFormatterFactory getTypeFormatterFactory() { + return typeFormatterFactory; + } + + @Override + public T load(T object, Map propertyConfigs) { + List properties = getProperties(object.getClass()); + for (Property property : properties) { + Object value = propertyConfigs.get(property.getName()); + if (value == null) { + throw new IllegalArgumentException("Config for property " + property.getName() + " is missing!"); + } + ObjectFormatter objectFormatter = property.getObjectFormatter(); + switch (property.getType()) { + case PropertyString: + Object fieldValue = objectFormatter.format(String.valueOf(value)); + try { + property.getField().set(object, fieldValue); + } catch (IllegalAccessException e) { + logger.warn("Set field " + property.getField() + " error!", e); + } + break; + case PropertyList: + if (!List.class.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("Config for property " + property.getName() + " should be subclass of List!"); + } + List listField = new ArrayList(); + List listConfigs = (List) value; + for (String listConfig : listConfigs) { + listField.add(objectFormatter.format(listConfig)); + } + try { + property.getField().set(object, listField); + } catch (IllegalAccessException e) { + logger.warn("Set field " + property.getField() + " error!", e); + } + break; + case PropertyMap: + if (!Map.class.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("Config for property " + property.getName() + " should be subclass of List!"); + } + Map mapField = new HashMap(); + Map mapConfigs = (Map) value; + for (Map.Entry entry : mapConfigs.entrySet()) { + mapField.put(entry.getKey(), objectFormatter.format(entry.getValue())); + } + try { + property.getField().set(object, mapField); + } catch (IllegalAccessException e) { + logger.warn("Set field " + property.getField() + " error!", e); + } + break; + } + } + return object; + } + + protected ObjectFormatter prepareTypeFormatterParam(TypeFormatter objectFormatter, String[] params) { + if (params == null) { + return objectFormatter; + } + return new ObjectFormatterWithParams().setTypeFormatter(objectFormatter).setParams(params); + } + + protected ObjectFormatter getObjectFormatter(Field field) { + Class type = field.getType(); + if (List.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) { + type = String.class; + } + if (field.isAnnotationPresent(Formatter.class)) { + Formatter formatter = field.getAnnotation(Formatter.class); + if (!formatter.formatter().equals(TypeFormatter.class)) { + TypeFormatter typeFormatter = typeFormatterFactory.getByFormatterClass(formatter.formatter()); + if (typeFormatter != null) { + return prepareTypeFormatterParam(typeFormatter,formatter.value()); + } + typeFormatterFactory.put(formatter.formatter()); + return prepareTypeFormatterParam(typeFormatterFactory.getByFormatterClass(formatter.formatter()), formatter.value()); + } else if (!formatter.subClazz().equals(String.class)) { + type = formatter.subClazz(); + TypeFormatter typeFormatter = typeFormatterFactory.get(type); + if (typeFormatter == null) { + throw new IllegalArgumentException("No typeFormatter for class " + type); + } + return prepareTypeFormatterParam(typeFormatter, formatter.value()); + } + } + return getTypeFormatterFactory().get(BasicTypeFormatter.detectBasicClass(type)); + } + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AnnotationPropertyLoader.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AnnotationPropertyLoader.java new file mode 100644 index 00000000..ea630b9c --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/AnnotationPropertyLoader.java @@ -0,0 +1,32 @@ +package us.codecraft.forger.property; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * @author code4crafter@gmail.com + */ +public class AnnotationPropertyLoader extends AbstractPropertyLoader { + + @Override + public List getProperties(Class clazz) { + Field[] fields = clazz.getDeclaredFields(); + List properties = new ArrayList(fields.length); + for (Field field : fields) { + Inject inject = field.getAnnotation(Inject.class); + if (inject != null) { + if (!field.isAccessible()) { + field.setAccessible(true); + } + Property property = Property.fromField(field); + if (inject.value().length() > 0) { + property.setName(inject.value()); + } + property.setObjectFormatter(getObjectFormatter(field)); + properties.add(property); + } + } + return properties; + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Inject.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Inject.java new file mode 100644 index 00000000..262e45a0 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Inject.java @@ -0,0 +1,16 @@ +package us.codecraft.forger.property; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @author code4crafter@gmail.com + */ +@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Inject { + + String value() default ""; + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Property.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Property.java new file mode 100644 index 00000000..66b196c8 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/Property.java @@ -0,0 +1,60 @@ +package us.codecraft.forger.property; + +import us.codecraft.forger.property.format.ObjectFormatter; + +import java.lang.reflect.Field; + +/** + * @author code4crafter@gmail.com + */ +public class Property { + + private String name; + + private PropertyType type; + + private Field field; + + private ObjectFormatter objectFormatter; + + public ObjectFormatter getObjectFormatter() { + return objectFormatter; + } + + public Property setObjectFormatter(ObjectFormatter objectFormatter) { + this.objectFormatter = objectFormatter; + return this; + } + + public String getName() { + return name; + } + + public Property setName(String name) { + this.name = name; + return this; + } + + public PropertyType getType() { + return type; + } + + public Property setType(PropertyType type) { + this.type = type; + return this; + } + + public Field getField() { + return field; + } + + public Property setField(Field field) { + this.field = field; + return this; + } + + public static Property fromField(Field field) { + return new Property().setName(field.getName()).setType(PropertyType.from(field.getType())).setField(field); + } + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyLoader.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyLoader.java new file mode 100644 index 00000000..226407a5 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyLoader.java @@ -0,0 +1,15 @@ +package us.codecraft.forger.property; + +import java.util.List; +import java.util.Map; + +/** + * @author code4crafter@gmail.com + */ +public interface PropertyLoader { + + public T load(T object, Map propertyConfigs); + + public List getProperties(Class clazz); + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyType.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyType.java new file mode 100644 index 00000000..aa0df51b --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/PropertyType.java @@ -0,0 +1,23 @@ +package us.codecraft.forger.property; + +import java.util.List; +import java.util.Map; + +/** + * @author code4crafter@gmail.com + */ +public enum PropertyType { + + PropertyString,PropertyMap,PropertyList; + + public static PropertyType from(Class clazz){ + if (Map.class.isAssignableFrom(clazz)){ + return PropertyMap; + } + if (List.class.isAssignableFrom(clazz)){ + return PropertyList; + } + return PropertyString; + } + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/SimpleFieldPropertyLoader.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/SimpleFieldPropertyLoader.java new file mode 100644 index 00000000..13ff68af --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/SimpleFieldPropertyLoader.java @@ -0,0 +1,28 @@ +package us.codecraft.forger.property; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +/** + * @author code4crafter@gmail.com + */ +public class SimpleFieldPropertyLoader extends AbstractPropertyLoader { + + @Override + public List getProperties(Class clazz) { + Field[] fields = clazz.getDeclaredFields(); + List properties = new ArrayList(fields.length); + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers())){ + continue; + } + if (!field.isAccessible()){ + field.setAccessible(true); + } + properties.add(Property.fromField(field).setObjectFormatter(getObjectFormatter(field))); + } + return properties; + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/BasicTypeFormatter.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/BasicTypeFormatter.java new file mode 100644 index 00000000..a6d0e5f8 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/BasicTypeFormatter.java @@ -0,0 +1,168 @@ +package us.codecraft.forger.property.format; + +import java.util.Arrays; +import java.util.List; + +/** + * @author code4crafter@gmail.com + * @since 0.3.2 + */ +public abstract class BasicTypeFormatter implements TypeFormatter { + + @Override + public T format(String text) { + if (text == null) { + return null; + } + text = text.trim(); + return formatTrimmed(text); + } + + @Override + public T format(String text, String[] params) { + return format(text); + } + + protected abstract T formatTrimmed(String raw); + + public static final List> basicTypeFormatters = Arrays.>asList(IntegerFormatter.class, + LongFormatter.class, DoubleFormatter.class, FloatFormatter.class, ShortFormatter.class, + CharactorFormatter.class, ByteFormatter.class, BooleanFormatter.class, DateFormatter.class, StringFormatter.class); + + public static Class detectBasicClass(Class type) { + if (type.equals(Integer.TYPE) || type.equals(Integer.class)) { + return Integer.class; + } else if (type.equals(Long.TYPE) || type.equals(Long.class)) { + return Long.class; + } else if (type.equals(Double.TYPE) || type.equals(Double.class)) { + return Double.class; + } else if (type.equals(Float.TYPE) || type.equals(Float.class)) { + return Float.class; + } else if (type.equals(Short.TYPE) || type.equals(Short.class)) { + return Short.class; + } else if (type.equals(Character.TYPE) || type.equals(Character.class)) { + return Character.class; + } else if (type.equals(Byte.TYPE) || type.equals(Byte.class)) { + return Byte.class; + } else if (type.equals(Boolean.TYPE) || type.equals(Boolean.class)) { + return Boolean.class; + } + return type; + } + + public static class IntegerFormatter extends BasicTypeFormatter { + @Override + public Integer formatTrimmed(String raw) { + return Integer.parseInt(raw); + } + + @Override + public Class clazz() { + return Integer.class; + } + } + + public static class LongFormatter extends BasicTypeFormatter { + @Override + public Long formatTrimmed(String raw) { + return Long.parseLong(raw); + } + + @Override + public Class clazz() { + return Long.class; + } + } + + public static class DoubleFormatter extends BasicTypeFormatter { + @Override + public Double formatTrimmed(String raw) { + return Double.parseDouble(raw); + } + + @Override + public Class clazz() { + return Double.class; + } + } + + public static class FloatFormatter extends BasicTypeFormatter { + @Override + public Float formatTrimmed(String raw) { + return Float.parseFloat(raw); + } + + @Override + public Class clazz() { + return Float.class; + } + } + + public static class ShortFormatter extends BasicTypeFormatter { + @Override + public Short formatTrimmed(String raw) { + return Short.parseShort(raw); + } + + @Override + public Class clazz() { + return Short.class; + } + } + + public static class CharactorFormatter extends BasicTypeFormatter { + @Override + public Character formatTrimmed(String raw) { + return raw.charAt(0); + } + + @Override + public Class clazz() { + return Character.class; + } + } + + public static class ByteFormatter extends BasicTypeFormatter { + @Override + public Byte formatTrimmed(String raw) { + return Byte.parseByte(raw, 10); + } + + @Override + public Class clazz() { + return Byte.class; + } + } + + public static class BooleanFormatter extends BasicTypeFormatter { + @Override + public Boolean formatTrimmed(String raw) { + return Boolean.parseBoolean(raw); + } + + @Override + public Class clazz() { + return Boolean.class; + } + } + + public static class StringFormatter implements TypeFormatter { + + @Override + public String format(String text) { + return text; + } + + @Override + public String format(String text, String[] params) { + return format(text); + } + + @Override + public Class clazz() { + return String.class; + } + } + + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/DateFormatter.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/DateFormatter.java new file mode 100644 index 00000000..f9bdd9fd --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/DateFormatter.java @@ -0,0 +1,35 @@ +package us.codecraft.forger.property.format; + +import org.apache.commons.lang3.time.DateUtils; + +import java.text.ParseException; +import java.util.Date; + +/** + * @author code4crafter@gmail.com + * @since 0.3.2 + */ +public class DateFormatter implements TypeFormatter { + + public static final String[] DEFAULT_PATTERN = new String[]{"yyyy-MM-dd HH:mm"}; + + @Override + public Date format(String text) { + return format(text,DEFAULT_PATTERN); + } + + @Override + public Date format(String text, String[] params) { + try { + return DateUtils.parseDate(text, params); + } catch (ParseException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public Class clazz() { + return Date.class; + } + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/Formatter.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/Formatter.java new file mode 100644 index 00000000..45b84b16 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/Formatter.java @@ -0,0 +1,39 @@ +package us.codecraft.forger.property.format; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Define how the result string is convert to an object for field. + * + * @author code4crafter@gmail.com
+ * @since 0.3.2 + */ +@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Formatter { + + /** + * Set formatter params. + * + * @return formatter params + */ + String[] value(); + + /** + * Specific the class of field of class of elements in collection for field.
+ * It is not necessary to be set because we can detect the class by class of field, + * unless you use a collection as a field.
+ * + * @return the class of field + */ + Class subClazz() default String.class; + + /** + * If there are more than one formatter for a class, just specify the implement. + * @return implement + */ + Class formatter() default TypeFormatter.class; + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatter.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatter.java new file mode 100644 index 00000000..a5a81340 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatter.java @@ -0,0 +1,9 @@ +package us.codecraft.forger.property.format; + +/** + * @author code4crafter@gmail.com + */ +public interface ObjectFormatter { + + T format(String text); +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatterWithParams.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatterWithParams.java new file mode 100644 index 00000000..051cc5de --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/ObjectFormatterWithParams.java @@ -0,0 +1,34 @@ +package us.codecraft.forger.property.format; + +/** + * @author code4crafter@gmail.com + */ +public class ObjectFormatterWithParams implements ObjectFormatter { + + private TypeFormatter typeFormatter; + + private String[] params; + + public TypeFormatter getTypeFormatter() { + return typeFormatter; + } + + public ObjectFormatterWithParams setTypeFormatter(TypeFormatter typeFormatter) { + this.typeFormatter = typeFormatter; + return this; + } + + public String[] getParams() { + return params; + } + + public ObjectFormatterWithParams setParams(String[] params) { + this.params = params; + return this; + } + + @Override + public T format(String text) { + return typeFormatter.format(text, params); + } +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatter.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatter.java new file mode 100644 index 00000000..e6e436d6 --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatter.java @@ -0,0 +1,12 @@ +package us.codecraft.forger.property.format; + +/** + * @author code4crafter@gmail.com + */ +public interface TypeFormatter extends ObjectFormatter { + + T format(String text, String[] params); + + Class clazz(); + +} diff --git a/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatterFactory.java b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatterFactory.java new file mode 100644 index 00000000..027d8fea --- /dev/null +++ b/webmagic-avalon/forger/src/main/java/us/codecraft/forger/property/format/TypeFormatterFactory.java @@ -0,0 +1,53 @@ +package us.codecraft.forger.property.format; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author code4crafter@gmail.com + * @since 0.3.2 + */ +public class TypeFormatterFactory { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + private Map objectFormatterMapWithPropertyAsKey = new ConcurrentHashMap(); + + private Map objectFormatterMapWithClassAsKey = new ConcurrentHashMap(); + + public TypeFormatterFactory() { + initFormatterMap(); + } + + private void initFormatterMap() { + for (Class basicTypeFormatter : BasicTypeFormatter.basicTypeFormatters) { + put(basicTypeFormatter); + } + put(DateFormatter.class); + } + + public synchronized void put(Class objectFormatterClazz) { + try { + TypeFormatter typeFormatter = objectFormatterClazz.newInstance(); + if (typeFormatter.clazz() != null) { + objectFormatterMapWithPropertyAsKey.put(typeFormatter.clazz(), typeFormatter); + } + objectFormatterMapWithClassAsKey.put(objectFormatterClazz, typeFormatter); + } catch (InstantiationException e) { + logger.error("Init objectFormatter error", e); + } catch (IllegalAccessException e) { + logger.error("Init objectFormatter error", e); + } + } + + public TypeFormatter get(Class clazz) { + return objectFormatterMapWithPropertyAsKey.get(clazz); + } + + public TypeFormatter getByFormatterClass(Class clazz) { + return objectFormatterMapWithClassAsKey.get(clazz); + } +} diff --git a/webmagic-avalon/forger/src/main/resources/log4j.xml b/webmagic-avalon/forger/src/main/resources/log4j.xml new file mode 100644 index 00000000..c2b5a2f5 --- /dev/null +++ b/webmagic-avalon/forger/src/main/resources/log4j.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Bar.java b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Bar.java new file mode 100644 index 00000000..3b51a5c8 --- /dev/null +++ b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Bar.java @@ -0,0 +1,47 @@ +package us.codecraft.forger; + +import us.codecraft.forger.property.Inject; +import us.codecraft.forger.property.format.Formatter; + +import java.util.List; +import java.util.Map; + +/** + * @author code4crafter@gmail.com + */ +public class Bar { + + @Inject("bar") + private String bar; + + @Inject + private List values; + + @Formatter(value = "", subClazz = Integer.class) + @Inject + private Map idMap; + + public String getBar() { + return bar; + } + + public void setBar(String bar) { + this.bar = bar; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public Map getIdMap() { + return idMap; + } + + public void setIdMap(Map idMap) { + this.idMap = idMap; + } +} diff --git a/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Foo.java b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Foo.java new file mode 100644 index 00000000..daa2e155 --- /dev/null +++ b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Foo.java @@ -0,0 +1,47 @@ +package us.codecraft.forger; + +import us.codecraft.forger.property.Inject; +import us.codecraft.forger.property.format.Formatter; + +/** + * @author code4crafter@gmail.com + */ +public class Foo implements Fooable{ + + @Formatter("") + @Inject("fooa") + private String foo; + + public static final String SOURCE_CODE="import us.codecraft.forger.*;\n" + + "import us.codecraft.forger.property.Inject;\n" + + "import us.codecraft.forger.property.Inject;\n" + + "import us.codecraft.forger.property.format.Formatter;\n" + + "\n" + + "/**\n" + + " * @author code4crafter@gmail.com\n" + + " */\n" + + "public class Foo implements Fooable{\n" + + "\n" + + " @Formatter(\"\")\n" + + " @Inject(\"fooa\")\n" + + " private String foo;\n" + + "\n" + + " public String getFoo() {\n" + + " return foo;\n" + + " }\n" + + "\n" + + " @Override\n" + + " public String foo() {\n" + + " return foo;\n" + + " }\n" + + "}"; + + public String getFoo() { + return foo; + } + + @Override + public String foo() { + return foo; + } +} diff --git a/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Fooable.java b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Fooable.java new file mode 100644 index 00000000..86c1d02a --- /dev/null +++ b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/Fooable.java @@ -0,0 +1,9 @@ +package us.codecraft.forger; + +/** + * @author code4crafter@gmail.com + */ +public interface Fooable { + + public String foo(); +} diff --git a/webmagic-avalon/forger/src/test/java/us/codecraft/forger/ForgerFactoryTest.java b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/ForgerFactoryTest.java new file mode 100644 index 00000000..50f248a2 --- /dev/null +++ b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/ForgerFactoryTest.java @@ -0,0 +1,66 @@ +package us.codecraft.forger; + +import com.google.common.collect.ImmutableMap; +import org.junit.Test; +import us.codecraft.forger.compiler.GroovyForgerCompiler; +import us.codecraft.forger.property.AnnotationPropertyLoader; +import us.codecraft.forger.property.SimpleFieldPropertyLoader; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.*; + +/** + * @author code4crafter@gmail.com + */ +public class ForgerFactoryTest { + + @Test + public void testForgerCreateByClassProperty() throws Exception { + ForgerFactory forgerFactory = new ForgerFactory(new SimpleFieldPropertyLoader(), null); + Forger forger = forgerFactory.create(Foo.class); + Foo foo = forger.forge(ImmutableMap.of("foo", "test")); + assertThat(foo.getFoo()).isEqualTo("test"); + } + + @Test + public void testForgerCreateByClassAnnotation() throws Exception { + ForgerFactory forgerFactory = new ForgerFactory(new AnnotationPropertyLoader(), null); + Forger forger = forgerFactory.create(Foo.class); + Foo foo = forger.forge(ImmutableMap.of("fooa", "test")); + assertThat(foo.getFoo()).isEqualTo("test"); + } + + @Test + public void testForgerCreateByClassAnnotationCompile() throws Exception { + ForgerFactory forgerFactory = new ForgerFactory(new AnnotationPropertyLoader(), new GroovyForgerCompiler()); + Forger forger = forgerFactory.compile(Foo.SOURCE_CODE); + Fooable foo = forger.forge(ImmutableMap.of("fooa", "test")); + Field field = forger.getClazz().getDeclaredField("foo"); + field.setAccessible(true); + assertThat(field.get(foo)).isEqualTo("test"); + assertThat(foo.foo()).isEqualTo("test"); + } + + @Test + public void testForgerCreateByClassAnnotationWithCollections() throws Exception { + ForgerFactory forgerFactory = new ForgerFactory(new AnnotationPropertyLoader(), null); + Forger forger = forgerFactory.create(Bar.class); + Map map = new HashMap(); + map.put("bar", "bar"); + Map submap = new HashMap(); + submap.put("1", "1"); + submap.put("2", "2"); + map.put("idMap", submap); + List sublist = new ArrayList(); + sublist.add("test"); + map.put("values", sublist); + Bar forge = forger.forge(map); + assertThat(forge.getValues().size() > 0); + assertThat(forge.getIdMap().get("1")).isEqualTo(1); + } +} diff --git a/webmagic-avalon/forger/src/test/java/us/codecraft/forger/compiler/GroovyForgerCompilerTest.java b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/compiler/GroovyForgerCompilerTest.java new file mode 100644 index 00000000..244c25f5 --- /dev/null +++ b/webmagic-avalon/forger/src/test/java/us/codecraft/forger/compiler/GroovyForgerCompilerTest.java @@ -0,0 +1,19 @@ +package us.codecraft.forger.compiler; + +import org.junit.Test; +import us.codecraft.forger.Foo; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author code4crafter@gmail.com + */ +public class GroovyForgerCompilerTest { + + @Test + public void testGroovyClassLoader() throws Exception { + GroovyForgerCompiler groovyForgerCompiler = new GroovyForgerCompiler(); + Class compiledClass = groovyForgerCompiler.compile(Foo.SOURCE_CODE); + assertThat(compiledClass.getName()).isEqualTo("Foo"); + } +} diff --git a/webmagic-avalon/forger/src/test/resources/log4j.xml b/webmagic-avalon/forger/src/test/resources/log4j.xml new file mode 100644 index 00000000..9084694e --- /dev/null +++ b/webmagic-avalon/forger/src/test/resources/log4j.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +