diff --git a/pom.xml b/pom.xml index 895acf7..c8d6880 100644 --- a/pom.xml +++ b/pom.xml @@ -1,105 +1,184 @@ - 4.0.0 - - - org.sonatype.oss - oss-parent - 9 - - - org.kametic - universalvisitor - 1.0 - jar - - Universal Visitor - - http://kametic.org/universalvisitor - - - Universal Visitor is tiny but powerful library allowing to easily "visit" - an object graph and extracting information with a simple Map/Reduce API. - - - 2014 - - - - LGPL 3.0 - http://www.gnu.org/copyleft/lesser.html - - - - - - Epo Jemba - epo.jemba@kametic.com - Kametic - www.kametic.com - - Developer - Owner - - - - Pierre Thirouin - pierre.thirouin@gmail.com - - Developer - - - - - - UTF-8 - 1.6 - 1.6 - - - - - junit - junit - 4.10 - test - - - org.assertj - assertj-core - 1.6.1 - test - - - - - - ${basedir}/src/main/resources - true - - - ${basedir} - META-INF - - LICENSE - NOTICE - - - - - - - org.apache.maven.plugins - maven-release-plugin - - - - - - - https://github.com/kametic/universalvisitor - scm:git:git://github.com/kametic/universalvisitor.git - scm:git:git@github.com:kametic/universalvisitor.git - HEAD - + 4.0.0 + + org.kametic + universalvisitor + 1.0.2-SNAPSHOT + jar + + Universal Visitor + + http://kametic.org/universalvisitor + + + Universal Visitor is tiny but powerful library allowing to easily "visit" + an object graph and extracting information with a simple Map/Reduce API. + + + 2014 + + + + LGPL 3.0 + http://www.gnu.org/copyleft/lesser.html + + + + + + Epo Jemba + epo.jemba@kametic.com + Kametic + www.kametic.com + + Developer + Owner + + + + Pierre Thirouin + pierre.thirouin@gmail.com + + Developer + + + + + + UTF-8 + 1.6 + 1.6 + + 1.6.5 + 1.6 + 2.10.3 + 2.2.1 + 2.5.2 + + + + + junit + junit + 4.10 + test + + + org.assertj + assertj-core + 1.6.1 + test + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + + ${basedir}/src/main/resources + true + + + ${basedir} + META-INF + + LICENSE + NOTICE + + + + + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + true + false + release + deploy + + + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + + + + + + + + https://github.com/kametic/universalvisitor + scm:git:git://github.com/kametic/universalvisitor.git + scm:git:git@github.com:kametic/universalvisitor.git + HEAD + diff --git a/src/main/java/org/kametic/universalvisitor/UniversalVisitor.java b/src/main/java/org/kametic/universalvisitor/UniversalVisitor.java index 43876a1..8d375e4 100644 --- a/src/main/java/org/kametic/universalvisitor/UniversalVisitor.java +++ b/src/main/java/org/kametic/universalvisitor/UniversalVisitor.java @@ -16,47 +16,30 @@ */ package org.kametic.universalvisitor; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - -import org.kametic.universalvisitor.api.Filter; -import org.kametic.universalvisitor.api.Job; -import org.kametic.universalvisitor.api.MapReduce; -import org.kametic.universalvisitor.api.Mapper; -import org.kametic.universalvisitor.api.Metadata; -import org.kametic.universalvisitor.api.Node; -import org.kametic.universalvisitor.api.Reducer; +import org.kametic.universalvisitor.api.*; import org.kametic.universalvisitor.core.JobDefault; import org.kametic.universalvisitor.core.MapReduceDefault; import org.kametic.universalvisitor.core.NodeDefault; +import java.lang.reflect.*; +import java.util.*; + /** * UniversalVisitor is the main entrypoint. With it you can visit any object graph instance. *

* It will first visit the object graph then produces a linked list of {@link Node}. + *

*

- * The Map Reduce pattern will then be applied from this linked list. Users can create their Map Reduce jobs by using the API provided. + * The Map Reduce pattern will then be applied from this linked list. Users can create their Map Reduce jobs by using the API provided. + *

* - * - * + * * @author Epo Jemba * @author Pierre Thirouin */ @@ -120,34 +103,31 @@ public void visit(Object o, Filter filter, MapReduce... mapReduces) visit(o, filter, new JobDefault(mapReduces)); } - @SuppressWarnings({ - "rawtypes" - }) - public void visit(AnnotatedElement ae, Filter filter, Job job) - { + @SuppressWarnings({ "rawtypes" }) + public void visit(AnnotatedElement ae, Filter filter, Job job){ Set cache = new HashSet(); - ChainedNode node = ChainedNode.createRoot(); + //ChainedNode Changed to linkedList (perf issues when accessing last element fix) + Queue nodes = new LinkedList(); if (filter == null) { filter = Filter.TRUE; } - - recursiveVisit(ae, cache, node, filter); - doMapReduce(job, node); + //The currentLevel is set at "-1" before the recursive instead of creating a dummy Node + recursiveVisit(ae, cache, nodes, filter, -1); + doMapReduce(job, nodes); } /** - * @param job - * @param node - * @return + * @param job the job + * @param nodes the nodes */ @SuppressWarnings({ "unchecked", "rawtypes" }) - private void doMapReduce(Job job, ChainedNode node) - { - for (node = node.next; node != null; node = node.next) - { + private void doMapReduce(Job job, Queue nodes) + { + for (LocalNode node : nodes) + { for (MapReduce mapReduce : job.mapReduces()) { if (mapReduce.getMapper().handle(node.annotatedElement())) @@ -170,69 +150,37 @@ public void visit(Object o, Filter filter, Job job) { Set cache = new HashSet(); - - ChainedNode node = ChainedNode.createRoot(); + //Changed to linkedList of LocalNode instead of the ChainedNode (perf issues fix) + Queue nodes = new LinkedList(); if (filter == null) { filter = Filter.TRUE; } - recursiveVisit(o, cache, node, filter); + recursiveVisit(o, cache, nodes, filter, -1); - doMapReduce(job, node); + doMapReduce(job, nodes); } - private static class ChainedNode extends NodeDefault - { - ChainedNode next; + //ChainedNode changed to localNode due to performance issues + private static class LocalNode extends NodeDefault + { - protected ChainedNode(Object instance, AnnotatedElement annotatedElement, int level, ChainedNode next) - { + protected LocalNode(Object instance, AnnotatedElement annotatedElement, int level) + { super(instance, annotatedElement, level); - this.next = next; } - private void next(ChainedNode node) - { - if (next != null) - { - throw new IllegalStateException("next pair can not be set twice."); - } - next = node; - } - public static ChainedNode createRoot() - { - return new ChainedNode(new Object(), null, -1, null); - } - - public ChainedNode append(Object o, AnnotatedElement ao, int level, Metadata metadata) - { - - next(new ChainedNode(o, ao, level, null).metadata(metadata)); - - return next; - } @Override - public ChainedNode metadata(Metadata metadata) + public LocalNode metadata(Metadata metadata) { - return (ChainedNode) super.metadata(metadata); + return (LocalNode) super.metadata(metadata); } - public ChainedNode last() - { - if (next != null) - { - return next.last(); - } - else - { - return this; - } - } @Override public String toString() @@ -243,13 +191,12 @@ public String toString() indentation += "\t"; } // instance()= String rep = String.format( - "%sChainedNode [ %s@%s , level=%s , annotatedElement=%s] \n%s", + "%sLocalNode [ %s@%s , level=%s , annotatedElement=%s] \n", indentation, instance().getClass().getSimpleName(), Integer.toHexString(instance().hashCode()), level(), - annotatedElement(), - next); + annotatedElement()); return rep; // return "ChainedNode [instance()=" + instance() + ", level()=" @@ -302,11 +249,9 @@ public String toString() // } // } - private void recursiveVisit(Object object, Set cache, ChainedNode node, Filter filter) + private void recursiveVisit(Object object, Set cache, Queue nodes, Filter filter, int currentLevel) { - - int currentLevel = node.level() + 1; - + currentLevel ++; if (!cache.contains(object)) { @@ -318,26 +263,26 @@ private void recursiveVisit(Object object, Set cache, ChainedNode node, } else if (Collection.class.isAssignableFrom(object.getClass())) { - visitAllCollection((Collection) object, cache, node, currentLevel, filter); + visitAllCollection((Collection) object, cache, nodes, currentLevel, filter); } else if (object.getClass().isArray()) { - visitAllArray(object, cache, node, currentLevel, filter); + visitAllArray(object, cache, nodes, currentLevel, filter); } else if (Map.class.isAssignableFrom(object.getClass())) { - visitAllMap((Map) object, cache, node, currentLevel, filter); + visitAllMap((Map) object, cache, nodes, currentLevel, filter); } else { - visitObject(object, cache, node, currentLevel, filter); + visitObject(object, cache, nodes, currentLevel, filter); } } } - private void visitObject(Object object, Set cache, ChainedNode node, int currentLevel, Filter filter) + private void visitObject(Object object, Set cache, Queue nodes, int currentLevel, Filter filter) { - visitObject(object, cache, node, currentLevel, filter, null); + visitObject(object, cache, nodes, currentLevel, filter, null); } // private void visitConstructor(Constructor ae, Set cache, ChainedNode node, int @@ -395,15 +340,14 @@ private void visitObject(Object object, Set cache, ChainedNode node, int // } // } - private void visitObject(Object object, Set cache, ChainedNode node, int currentLevel, Filter filter, Metadata metadata) - { + private void visitObject(Object object, Set cache, Queue nodes, int currentLevel, Filter filter, Metadata metadata) + { Class currentClass = object.getClass(); if (!isJdkMember(currentClass)) { - ChainedNode current = node; Class[] family = getAllInterfacesAndClasses(currentClass); for (Class elementClass : family) { // We iterate over all the family tree of the current class @@ -415,7 +359,7 @@ private void visitObject(Object object, Set cache, ChainedNode node, int { if (!isJdkMember(c) && !c.isSynthetic()) { - current = current.append(object, c, currentLevel, metadata); + nodes.add(new LocalNode(object, c, currentLevel).metadata(metadata)); } } // @@ -423,7 +367,7 @@ private void visitObject(Object object, Set cache, ChainedNode node, int { if (!isJdkMember(m) && !m.isSynthetic()) { - current = current.append(object, m, currentLevel, metadata); + nodes.add(new LocalNode(object, m, currentLevel).metadata(metadata)); } } @@ -431,15 +375,14 @@ private void visitObject(Object object, Set cache, ChainedNode node, int { if (!isJdkMember(f) && !f.isSynthetic()) { + nodes.add(new LocalNode(object, f, currentLevel).metadata(metadata)); - current = current.append(object, f, currentLevel, metadata); if (filter != null && filter.retains(f)) { Object deeperObject = readField(f, object); - recursiveVisit(deeperObject, cache, current, filter); - current = current.last(); + recursiveVisit(deeperObject, cache, nodes, filter, currentLevel); } } } @@ -449,9 +392,8 @@ private void visitObject(Object object, Set cache, ChainedNode node, int } } - private void visitAllCollection(Collection collection, Set cache, ChainedNode node, int currentLevel, Filter filter) - { - ChainedNode current = node; + private void visitAllCollection(Collection collection, Set cache, Queue nodes, int currentLevel, Filter filter) + { boolean indexable = collection instanceof List || collection instanceof Queue; @@ -463,45 +405,38 @@ private void visitAllCollection(Collection collection, Set cache, Cha { if (indexable) { - visitObject(value, cache, current, currentLevel, filter, new Metadata(i)); + visitObject(value, cache, nodes, currentLevel, filter, new Metadata(i)); } else { - visitObject(value, cache, current, currentLevel, filter); + visitObject(value, cache, nodes, currentLevel, filter); } - current = current.last(); } } } - private void visitAllArray(Object arrayObject, Set cache, ChainedNode node, int currentLevel, Filter filter) + private void visitAllArray(Object arrayObject, Set cache, Queue nodes, int currentLevel, Filter filter) { - ChainedNode current = node; - int l = Array.getLength(arrayObject); for (int i = 0; i < l; i++) { Object value = Array.get(arrayObject, i); if (value != null) { - visitObject(value, cache, current, currentLevel, filter, new Metadata(i)); - current = current.last(); + visitObject(value, cache, nodes, currentLevel, filter, new Metadata(i)); } } } - private void visitAllMap(Map values, Set cache, ChainedNode pair, int currentLevel, Filter filter) + private void visitAllMap(Map values, Set cache, Queue nodes, int currentLevel, Filter filter) { - ChainedNode current = pair; for (Object thisKey : values.keySet()) { Object value = values.get(thisKey); if (value != null) { - visitObject(thisKey, cache, current, currentLevel, filter); - current = current.last(); - visitObject(value, cache, current, currentLevel, filter, new Metadata(thisKey)); - current = current.last(); + visitObject(thisKey, cache, nodes, currentLevel, filter); + visitObject(value, cache, nodes, currentLevel, filter, new Metadata(thisKey)); } } } @@ -539,8 +474,7 @@ private Object readField(Field f, Object instance) /** * Returns all the interfaces and classes implemented or extended by a class. * - * @param clazz - * The class to search from. + * @param clazz The class to search from. * @return The array of classes and interfaces found. */ private Class[] getAllInterfacesAndClasses(Class clazz) @@ -554,8 +488,7 @@ private Class[] getAllInterfacesAndClasses(Class clazz) * This method walks up the inheritance hierarchy to make sure we get every class/interface extended or * implemented by classes. * - * @param classes - * The classes array used as search starting point. + * @param classes The classes array used as search starting point. * @return the found classes and interfaces. */ @SuppressWarnings("unchecked") diff --git a/src/main/java/org/kametic/universalvisitor/api/MapReduce.java b/src/main/java/org/kametic/universalvisitor/api/MapReduce.java index c032732..9578a7c 100644 --- a/src/main/java/org/kametic/universalvisitor/api/MapReduce.java +++ b/src/main/java/org/kametic/universalvisitor/api/MapReduce.java @@ -19,16 +19,17 @@ /** * A MapReduce of T represents : *
    - *
  • one Mapper of <T>
  • - *
  • one ore more Reducer of <T,?>
  • + *
  • one Mapper of <T>
  • + *
  • one ore more Reducer of <T,?>
  • *
* It has a method aggregate() whose purpose is to aggregate all the reduction into one Aggregation. *

* MapReduceDefault is the implementation we supply. - * + *

+ * * @author epo.jemba * - * @param + * @param the object type to map/reduce */ public interface MapReduce { diff --git a/src/main/java/org/kametic/universalvisitor/api/Mapper.java b/src/main/java/org/kametic/universalvisitor/api/Mapper.java index 93ec7d1..b70818e 100644 --- a/src/main/java/org/kametic/universalvisitor/api/Mapper.java +++ b/src/main/java/org/kametic/universalvisitor/api/Mapper.java @@ -22,7 +22,8 @@ * A Mapper will "map" all the AnnotatedElement of the visited object graph, it will allows. *

* All results of the map(Node) method will be then given to one or more Reducers. - * + *

+ * * @author Epo Jemba * @author Pierre Thirouin */ @@ -40,7 +41,7 @@ public interface Mapper /** * The implementation of the actual mapping given the node in parameter. * - * @param node + * @param node the node * @return the actual result of the Map. */ O map(Node node); diff --git a/src/main/java/org/kametic/universalvisitor/api/Metadata.java b/src/main/java/org/kametic/universalvisitor/api/Metadata.java index 9fb74dd..d2deada 100644 --- a/src/main/java/org/kametic/universalvisitor/api/Metadata.java +++ b/src/main/java/org/kametic/universalvisitor/api/Metadata.java @@ -20,8 +20,8 @@ * * Metadata is the object held by the Node that keeps metadata like *
    - *
  • if needed the index, if the element is inside an array or alist. - *
  • if needed the key, if the element is inside a Map. + *
  • if needed the index, if the element is inside an array or alist.
  • + *
  • if needed the key, if the element is inside a Map.
  • *
* * @author epo.jemba@kametic.com @@ -43,7 +43,7 @@ public Metadata() /** * Constructor with a key as parameter. * - * @param key + * @param key the key */ public Metadata(Object key) { @@ -53,7 +53,7 @@ public Metadata(Object key) /** * Constructor with an index as parameter. * - * @param index + * @param index the index */ public Metadata(int index) { diff --git a/src/main/java/org/kametic/universalvisitor/api/Node.java b/src/main/java/org/kametic/universalvisitor/api/Node.java index 922d6ae..f2fcb56 100644 --- a/src/main/java/org/kametic/universalvisitor/api/Node.java +++ b/src/main/java/org/kametic/universalvisitor/api/Node.java @@ -19,15 +19,15 @@ import java.lang.reflect.AnnotatedElement; /** - * * A node is the element type of the internal linked list created during the visit of the object graph. *

- * It has : + * It has: + *

*
    - *
  • the visited annotated element : Field, Method, Constructor, Class or Package. - *
  • the Metadata associated with the node. - *
  • the level inside the graph visited it starts with 0. - *
  • the instance of the visited annotated element. + *
  • the visited annotated element : Field, Method, Constructor, Class or Package.
  • + *
  • the Metadata associated with the node.
  • + *
  • the level inside the graph visited it starts with 0.
  • + *
  • the instance of the visited annotated element.
  • *
* * @author epo.jemba@kametic.com diff --git a/src/test/java/org/nuunframework/universalvisitor/UniversalVisitorTest.java b/src/test/java/org/nuunframework/universalvisitor/UniversalVisitorTest.java index 2fcad99..f53f6c0 100644 --- a/src/test/java/org/nuunframework/universalvisitor/UniversalVisitorTest.java +++ b/src/test/java/org/nuunframework/universalvisitor/UniversalVisitorTest.java @@ -1,32 +1,12 @@ package org.nuunframework.universalvisitor; -import static org.assertj.core.api.Assertions.assertThat; - -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; - import org.junit.Before; import org.junit.Test; import org.kametic.universalvisitor.UniversalVisitor; -import org.kametic.universalvisitor.api.Filter; -import org.kametic.universalvisitor.api.MapReduce; -import org.kametic.universalvisitor.api.Mapper; -import org.kametic.universalvisitor.api.Metadata; -import org.kametic.universalvisitor.api.Node; -import org.kametic.universalvisitor.api.Reducer; +import org.kametic.universalvisitor.api.*; import org.kametic.universalvisitor.core.MapReduceDefault; import org.nuunframework.universalvisitor.sample.Alphabet; -import org.nuunframework.universalvisitor.sample.collections.H; -import org.nuunframework.universalvisitor.sample.collections.I; -import org.nuunframework.universalvisitor.sample.collections.J; -import org.nuunframework.universalvisitor.sample.collections.K; -import org.nuunframework.universalvisitor.sample.collections.L; +import org.nuunframework.universalvisitor.sample.collections.*; import org.nuunframework.universalvisitor.sample.issues.Issue1; import org.nuunframework.universalvisitor.sample.issues.Issue2; import org.nuunframework.universalvisitor.sample.levels.L1; @@ -40,6 +20,17 @@ import org.nuunframework.universalvisitor.sample.simple.B; import org.nuunframework.universalvisitor.sample.simple.C; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + /** * * @@ -48,21 +39,13 @@ public class UniversalVisitorTest { UniversalVisitor underTest; - A a; - D d; - H h; - M m; - N n; - Issue1 issue1; - Issue2 issue2; - L1 l1; @SuppressWarnings("serial") @@ -251,7 +234,6 @@ public void issue1() public void issue2() { CountMapper nopMap = new CountMapper(); - // System.out.println("============================== ISSUE2 ==================================="); underTest.visit(issue2, nopMap); assertThat(nopMap.methods).contains( @@ -266,7 +248,6 @@ public void issue2() "parentProtected", "parentPackage", "parentPublic"); - // System.out.println(nopMap.methods); assertThat(nopMap.fields).contains( "interface1F", "interface2F", @@ -279,8 +260,6 @@ public void issue2() "parentProtected", "parentPackage", "parentPublic"); - // System.out.println(nopMap.fields); - System.out.println(nopMap.node); } @Test @@ -290,6 +269,37 @@ public void checkLevel() underTest.visit(l1, mapper); } + + @Test + public void performance_test() { + P p = new P(); + p.k = new K(); + p.j = new J(); + p.os = new ArrayList(); + for (int i = 0; i < 10000; i++) { + O o = new O(); + o.k = new K(); + o.j = new J(); + o.p = new P(); + o.p.j = new J(); + o.p.os = new ArrayList() { + { + add(new O()); + add(new O()); + } + }; + p.os.add(o); + } + + MyPredicate predicate = new MyPredicate(); + MyMapper mapper = new MyMapper(); + SumReducer reducer = new SumReducer(); + + underTest.visit(p, predicate, mapper, reducer); + + assertThat(mapper.getMaxLevel()).isEqualTo(3); + } + class CheckLevelMap implements Mapper { @@ -324,13 +334,10 @@ public Void map(Node node) { metadata = new Metadata(); } - System.out.println(indentation + "|" + node.level() + "|" + " " + f.getName() + metadata + value + " from " - + f.getDeclaringClass().getSimpleName()); } if (node.annotatedElement() instanceof Constructor) { Constructor c = (Constructor) node.annotatedElement(); - System.out.println(indentation + "|" + node.level() + "|" + node.metadata() + " " + c.getDeclaringClass().getSimpleName() + "()"); } return null; @@ -349,7 +356,6 @@ public boolean handle(AnnotatedElement object) @Override public Void map(Node node) { - System.out.println("Current Node : " + node.annotatedElement()); return null; } @@ -414,7 +420,6 @@ public Void map(Node node) try { value = (Integer) f.get(node.instance()); - System.out.println("value " + value); f.set(node.instance(), value * 10); } catch (IllegalArgumentException e) @@ -473,7 +478,6 @@ static class MyMapper implements Mapper @Override public Integer map(Node node) { - System.out.println("node " + node.annotatedElement() + " -> " + node.instance() + " type = " + node.instance().getClass()); counter++; maxLevel = Math.max(maxLevel, node.level()); diff --git a/src/test/java/org/nuunframework/universalvisitor/sample/collections/O.java b/src/test/java/org/nuunframework/universalvisitor/sample/collections/O.java new file mode 100644 index 0000000..5ff25e7 --- /dev/null +++ b/src/test/java/org/nuunframework/universalvisitor/sample/collections/O.java @@ -0,0 +1,11 @@ +package org.nuunframework.universalvisitor.sample.collections; + +import org.nuunframework.universalvisitor.sample.Alphabet; + +@Alphabet +public class O { + + public K k; + public J j; + public P p; +} diff --git a/src/test/java/org/nuunframework/universalvisitor/sample/collections/P.java b/src/test/java/org/nuunframework/universalvisitor/sample/collections/P.java new file mode 100644 index 0000000..d708d65 --- /dev/null +++ b/src/test/java/org/nuunframework/universalvisitor/sample/collections/P.java @@ -0,0 +1,12 @@ +package org.nuunframework.universalvisitor.sample.collections; + +import java.util.Collection; + +import org.nuunframework.universalvisitor.sample.Alphabet; + +@Alphabet +public class P { + public Collection os; + public K k; + public J j; +}