-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Performance] Export Model constructor throws exceptions on very hot paths #127
Changes from 4 commits
d17f14c
89555a3
8d3f598
d76dd8c
bc2d752
7c2f119
90760c3
57dfafc
7821e15
711abf9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,12 +42,22 @@ public class ModelBuilder { | |
/*package*/ final Map<Class, Model> models = new ConcurrentHashMap<Class, Model>(); | ||
|
||
public <T> Model<T> get(Class<T> type) throws NotExportableException { | ||
if (type.getAnnotation(ExportedBean.class) == null) { | ||
throw new NotExportableException(type); | ||
} | ||
return get(type, null, null); | ||
} | ||
|
||
public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why removing the |
||
public <T> Model<T> get(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if (type.getAnnotation(ExportedBean.class) == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you rather meant to write Model<T> model = getOrNull(type, propertyOwner, property);
if (model == null) {
throw new NotExportableException(type);
}
return model; |
||
throw new NotExportableException(type); | ||
} | ||
return getOrNull(type, propertyOwner, property); | ||
} | ||
|
||
public <T> Model<T> getOrNull(Class<T> type, @CheckForNull Class<?> propertyOwner, @Nullable String property) throws NotExportableException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
Model m = models.get(type); | ||
if(m==null) { | ||
if(m==null && type.getAnnotation(ExportedBean.class) != null) { | ||
m = new Model<T>(this, type, propertyOwner, property); | ||
} | ||
return m; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,23 +131,22 @@ public void writeTo(Object object, TreePruner pruner, DataWriter writer) throws | |
TreePruner child = pruner.accept(object, this); | ||
if (child==null) return; | ||
|
||
Object d = writer.getExportConfig().getExportInterceptor().getValue(this,object, writer.getExportConfig()); | ||
Object d = writer.getExportConfig().getExportInterceptor().getValue(this,object,writer.getExportConfig()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gratuitous, revert |
||
|
||
if ((d==null && skipNull) || d == ExportInterceptor.SKIP) { // don't write anything | ||
return; | ||
} | ||
if (merge) { | ||
// merged property will get all its properties written here | ||
if (d != null) { | ||
Model model; | ||
try { | ||
model = owner.get(d.getClass(), parent.type, name); | ||
} catch (NotExportableException e) { | ||
if (d.getClass().getAnnotation(ExportedBean.class) == null) { | ||
if(writer.getExportConfig().isSkipIfFail()){ | ||
return; | ||
} else { | ||
throw new NotExportableException(d.getClass()); | ||
} | ||
throw e; | ||
} | ||
Model model = owner.getOrNull(d.getClass(), parent.type, name); | ||
model.writeNestedObjectTo(d, new FilteringTreePruner(parent.HAS_PROPERTY_NAME_IN_ANCESTRY,child), writer); | ||
} | ||
} else { | ||
|
@@ -199,118 +198,126 @@ private void writeValue(Type expected, Object value, TreePruner pruner, DataWrit | |
|
||
Class c = value.getClass(); | ||
|
||
Model model; | ||
try { | ||
model = owner.get(c, parent.type, name); | ||
} catch (NotExportableException ex) { | ||
if(STRING_TYPES.contains(c)) { | ||
writer.value(value.toString()); | ||
return; | ||
} | ||
if(PRIMITIVE_TYPES.contains(c)) { | ||
writer.valuePrimitive(value); | ||
return; | ||
if (c.getAnnotation(ExportedBean.class) == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Am I missing something or would this not be caught by the null return value from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are absolutely right. Fixed. |
||
handleNotExportable(expected, value, pruner, writer, skipIfFail, c); | ||
} else { | ||
try { | ||
writer.type(expected, value.getClass()); | ||
} catch (AbstractMethodError _) { | ||
// legacy impl that doesn't understand it | ||
} | ||
Class act = c.getComponentType(); | ||
if (act !=null) { // array | ||
Range r = pruner.getRange(); | ||
writer.startArray(); | ||
if (value instanceof Object[]) { | ||
// typical case | ||
for (Object item : r.apply((Object[]) value)) { | ||
writeBuffered(act, item, pruner, writer); | ||
} | ||
} else { | ||
// more generic case | ||
int len = Math.min(r.max, Array.getLength(value)); | ||
for (int i=r.min; i<len; i++) { | ||
writeBuffered(act, Array.get(value, i), pruner, writer); | ||
} | ||
try { | ||
Model model = owner.getOrNull(c, parent.type, name); | ||
if (model == null) { | ||
throw new NotExportableException(c); | ||
} | ||
writer.endArray(); | ||
writer.startObject(); | ||
model.writeNestedObjectTo(value, pruner, writer); | ||
writer.endObject(); | ||
} catch (NotExportableException ex) { | ||
handleNotExportable(expected, value, pruner, writer, skipIfFail, c); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. reduced diff is easier to read here (generally dislike patches that change indentation of large existing code blocks but sometimes it is hard to avoid) |
||
return; | ||
} | ||
if(value instanceof Iterable) { | ||
writer.startArray(); | ||
Type expectedItemType = Types.getTypeArgument(expected, 0, null); | ||
for (Object item : pruner.getRange().apply((Iterable) value)) { | ||
writeBuffered(expectedItemType, item, pruner, writer); | ||
} | ||
} | ||
|
||
private void handleNotExportable(Type expected, Object value, TreePruner pruner, DataWriter writer, boolean skipIfFail, Class c) throws IOException { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The method name is potentially misleading. Really this method is handling anything without There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed and added a description. |
||
if(STRING_TYPES.contains(c)) { | ||
writer.value(value.toString()); | ||
return; | ||
} | ||
if(PRIMITIVE_TYPES.contains(c)) { | ||
writer.valuePrimitive(value); | ||
return; | ||
} | ||
Class act = c.getComponentType(); | ||
if (act !=null) { // array | ||
Range r = pruner.getRange(); | ||
writer.startArray(); | ||
if (value instanceof Object[]) { | ||
// typical case | ||
for (Object item : r.apply((Object[]) value)) { | ||
writeBuffered(act, item, pruner, writer); | ||
} | ||
writer.endArray(); | ||
return; | ||
} else { | ||
// more generic case | ||
int len = Math.min(r.max, Array.getLength(value)); | ||
for (int i=r.min; i<len; i++) { | ||
writeBuffered(act, Array.get(value, i), pruner, writer); | ||
} | ||
} | ||
writer.endArray(); | ||
return; | ||
} | ||
if(value instanceof Iterable) { | ||
writer.startArray(); | ||
Type expectedItemType = Types.getTypeArgument(expected, 0, null); | ||
for (Object item : pruner.getRange().apply((Iterable) value)) { | ||
writeBuffered(expectedItemType, item, pruner, writer); | ||
} | ||
if(value instanceof Map) { | ||
if (verboseMap!=null) {// verbose form | ||
writer.startArray(); | ||
for (Map.Entry e : ((Map<?,?>) value).entrySet()) { | ||
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig()); | ||
try { | ||
writeStartObjectNullType(buffer); | ||
buffer.name(verboseMap[0]); | ||
writeValue(null, e.getKey(), pruner, buffer); | ||
buffer.name(verboseMap[1]); | ||
writeValue(null, e.getValue(), pruner, buffer); | ||
buffer.endObject(); | ||
buffer.finished(); | ||
} catch (IOException x) { | ||
if (x.getCause() instanceof InvocationTargetException) { | ||
LOGGER.log(Level.WARNING, "skipping export of " + e, x); | ||
} | ||
writer.endArray(); | ||
return; | ||
} | ||
if(value instanceof Map) { | ||
if (verboseMap!=null) {// verbose form | ||
writer.startArray(); | ||
for (Map.Entry e : ((Map<?,?>) value).entrySet()) { | ||
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig()); | ||
try { | ||
writeStartObjectNullType(buffer); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard to follow this long diff, but IIUC you are basically just extracting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually the whitespace-reduced diff is more approachable; there must have been a change in indentation for a long block of code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually, Im going to revert the method extraction. Still too long for my taste but should preserve the diff. |
||
buffer.name(verboseMap[0]); | ||
writeValue(null, e.getKey(), pruner, buffer); | ||
buffer.name(verboseMap[1]); | ||
writeValue(null, e.getValue(), pruner, buffer); | ||
buffer.endObject(); | ||
buffer.finished(); | ||
} catch (IOException x) { | ||
if (x.getCause() instanceof InvocationTargetException) { | ||
LOGGER.log(Level.WARNING, "skipping export of " + e, x); | ||
} | ||
buffer.commit(writer); | ||
} | ||
writer.endArray(); | ||
} else {// compact form | ||
writeStartObjectNullType(writer); | ||
for (Map.Entry e : ((Map<?,?>) value).entrySet()) { | ||
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig()); | ||
try { | ||
buffer.name(e.getKey().toString()); | ||
writeValue(null, e.getValue(), pruner, buffer); | ||
buffer.finished(); | ||
} catch (IOException x) { | ||
if (x.getCause() instanceof InvocationTargetException) { | ||
LOGGER.log(Level.WARNING, "skipping export of " + e, x); | ||
} | ||
buffer.commit(writer); | ||
} | ||
writer.endArray(); | ||
} else {// compact form | ||
writeStartObjectNullType(writer); | ||
for (Map.Entry e : ((Map<?,?>) value).entrySet()) { | ||
BufferedDataWriter buffer = new BufferedDataWriter(writer.getExportConfig()); | ||
try { | ||
buffer.name(e.getKey().toString()); | ||
writeValue(null, e.getValue(), pruner, buffer); | ||
buffer.finished(); | ||
} catch (IOException x) { | ||
if (x.getCause() instanceof InvocationTargetException) { | ||
LOGGER.log(Level.WARNING, "skipping export of " + e, x); | ||
} | ||
buffer.commit(writer); | ||
} | ||
writer.endObject(); | ||
buffer.commit(writer); | ||
} | ||
return; | ||
} | ||
if(value instanceof Date) { | ||
writer.valuePrimitive(((Date) value).getTime()); | ||
return; | ||
} | ||
if(value instanceof Calendar) { | ||
writer.valuePrimitive(((Calendar) value).getTimeInMillis()); | ||
return; | ||
} | ||
if(value instanceof Enum) { | ||
writer.value(value.toString()); | ||
return; | ||
} | ||
|
||
if (skipIfFail) { | ||
writer.startObject(); | ||
writer.endObject(); | ||
return; | ||
} | ||
|
||
throw ex; | ||
return; | ||
} | ||
|
||
try { | ||
writer.type(expected, value.getClass()); | ||
} catch (AbstractMethodError _) { | ||
// legacy impl that doesn't understand it | ||
if(value instanceof Date) { | ||
writer.valuePrimitive(((Date) value).getTime()); | ||
return; | ||
} | ||
if(value instanceof Calendar) { | ||
writer.valuePrimitive(((Calendar) value).getTimeInMillis()); | ||
return; | ||
} | ||
if(value instanceof Enum) { | ||
writer.value(value.toString()); | ||
return; | ||
} | ||
|
||
if (skipIfFail) { | ||
writer.startObject(); | ||
writer.endObject(); | ||
return; | ||
} | ||
|
||
writer.startObject(); | ||
model.writeNestedObjectTo(value, pruner, writer); | ||
writer.endObject(); | ||
throw new NotExportableException(c); | ||
} | ||
|
||
private static class BufferedDataWriter implements DataWriter { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not redundant? AFAICT the method it delegates to does the same thing.