stack overflow error getting points of MPolyline

User 870ab5b546

25-09-2012 01:44:15

I am processing the MObjects in the attached MRV document.  I am looking to see if the endpoints of any arrows are near the midpoints of any other arrows.  The code:


    private void extractArrows() throws MechError {
final String SELF = "MechData.extractArrows: ";
arrows = new ArrayList<MechArrow>();
final List<MechArrow> branchedArrows = new ArrayList<MechArrow>();
final List<MechArrow> unsureArrows = new ArrayList<MechArrow>();
// search objects in MDoc for arrows
for (int objIndex = 0; objIndex < mechDoc.getObjectCount(); objIndex++) {
final MObject mObject = mechDoc.getObject(objIndex);
if (MolString.isLineOrRxnArrow(mObject)) {
if (MolString.isReactionArrow(mObject)) {
debugPrint(SELF + "examining arrow with object number ",
objIndex + 1);
final MechArrow arrow =
new MechArrow((MPolyline) mObject, objIndex);
final MPoint arrowTail = arrow.getPoint(TAIL);
if (arrowTail instanceof MRectanglePoint) { // box to box arrow
debugPrint(SELF + "arrow starts at box");
arrows.add(arrow);
} else if (arrowTail instanceof MMidPoint) {
// branched arrow
debugPrint(SELF + "arrow starts at arrow midpoint");
branchedArrows.add(arrow);
} else {
debugPrint(SELF + "arrow starts in space");
unsureArrows.add(arrow);
} // if arrow originates from another arrow
final MPoint arrowHead = arrow.getPoint(HEAD);
if (arrowHead instanceof MMidPoint)
fail(objIndex, "Reaction arrows may originate "
+ "from the midpoint of another arrow, "
+ "but they must point to a rectangle.");
} else { // line but not arrow
fail(objIndex, "Please connect your boxes with "
+ "reaction or resonance arrows, not with "
+ "straight lines.");
} // if is an arrow
} // if is a straight line or arrow
} // for each object in the MDocument
// see if any of the unassigned arrows are originating near the midpoint
// of another arrow
debugPrint(SELF + "assigning ", unsureArrows.size(),
" arrow(s) originating in space.");
for (int arrowNum = unsureArrows.size() - 1; arrowNum >= 0; arrowNum--) {
final MechArrow unsureArrow = unsureArrows.remove(arrowNum);
debugPrint(SELF + "finding origin of arrow ", arrowNum + 1,
" with object number ", unsureArrow.getObjectNumber() + 1);
final MPoint arrowTail = unsureArrow.getPoint(TAIL);
boolean foundMainArrow = false;
// search arrows known to originate at rectangles
for (MechArrow toRectArrow : arrows) {
if (onArrow(arrowTail, toRectArrow)) {
debugPrint(SELF + "arrow ", arrowNum + 1, " with object "
+ "number ", unsureArrow.getObjectNumber() + 1,
" has tail that originates on arrow with object "
+ "number ", toRectArrow.getObjectNumber() + 1,
"; this latter arrow originates at a rectangle.");
foundMainArrow = true;
unsureArrow.getArrow().setPoints(new MPoint[]
{toRectArrow.getPoint(MIDPT),
unsureArrow.getPoint(HEAD)});
branchedArrows.add(unsureArrow);
break;
} // if the unsure arrow originates at an arrow originating at a rectangle
} // for each arrow originating at a rectangle
// search arrows known to originate at other arrows
if (!foundMainArrow) for (MechArrow otherArrow : branchedArrows) {
if (onArrow(arrowTail, otherArrow)) {
debugPrint(SELF + "arrow ", arrowNum + 1, " with object "
+ "number ", unsureArrow.getObjectNumber() + 1,
" has tail that originates on arrow with object "
+ "number ", otherArrow.getObjectNumber() + 1,
"; this latter arrow originates at a branching "
+ "arrow.");
foundMainArrow = true;
unsureArrow.getArrow().setPoints(new MPoint[]
{otherArrow.getPoint(MIDPT),
unsureArrow.getPoint(HEAD)});
branchedArrows.add(unsureArrow);
break;
} // if the unsure arrow originates at an arrow originating at another arrow
} // for each arrow not originating at a rectangle
// search arrows with thus-far-unknown origins
if (!foundMainArrow) for (MechArrow otherArrow : unsureArrows) {
if (onArrow(arrowTail, otherArrow)) {
debugPrint(SELF + "arrow ", arrowNum + 1, " with object "
+ "number ", unsureArrow.getObjectNumber() + 1,
" has tail that originates on arrow with object "
+ "number ", otherArrow.getObjectNumber() + 1,
"; this latter arrow has an unknown origin.");
foundMainArrow = true;
unsureArrow.getArrow().setPoints(new MPoint[]
{otherArrow.getPoint(MIDPT),
unsureArrow.getPoint(HEAD)});
branchedArrows.add(unsureArrow);
break;
} // if the unsure arrow originates on an arrow originating in space
} // for each arrow not originating at a rectangle
if (!foundMainArrow) {
// assume it originates at a rectangle
arrows.add(unsureArrow);
} // if the unsure arrow doesn't originate at an arrow
} // for each unassigned arrow
removeBranchedArrows(branchedArrows);
setArrowEndpoints();
} // extractArrows()

private boolean onArrow(MPoint pt, MechArrow arrow) {
final String SELF = "MechData.onArrow: ";
debugPrint(SELF + "seeing if point is on arrow.");
final boolean onArrow =
VectorMath.pointOnLine(pt.getLocation(), arrow.getArrow());
debugPrint(SELF + "point is ", onArrow ? "" : "not ", "on arrow.");
return onArrow;
} // onArrow(MPoint, MechArrow)

public static boolean pointOnLine(DPoint3 p, MPolyline line) {
final String SELF = "VectorMath.pointOnLine: ";
debugPrint(SELF + "point = ", p, ", line = ", line);
final DPoint3 pt1 = line.getPoint(0).getLocation();
final DPoint3 pt2 = line.getPoint(1).getLocation();
final DPoint3 pt = new DPoint3(p.x, p.y, 0); // project onto XY plane
final DPoint3 vecTo1 = diff(new DPoint3(pt1.x, pt1.y, 0), pt);
final DPoint3 vecTo2 = diff(new DPoint3(pt2.x, pt2.y, 0), pt);
final int sumAngles = MathMethods.roundToInt(
toDegrees(Math.abs(angle(vecTo1, vecTo2))));
debugPrint(SELF + "sumAngles = ", sumAngles);
return MathMethods.inRange(sumAngles, new int[] {170, 190});
} // pointOnLine(DPoint3, MPolyline)

private static void debugPrint(Object... msg) {
Utils.printToLog(msg);
}

public static void printToLog(Object[] msg) {
printToLog(msg, SMILES);
} // alwaysPrint(Object[], String)

private static void printObject(Object obj, String format) {
if (obj == null) {
System.out.print("null");
...
} else if (obj instanceof DPoint3) {
System.out.print(((DPoint3) obj).toString());
} else if (obj instanceof MPoint) {
printObject(((MPoint) obj).getLocation(), format); // recursive call
} else if (obj instanceof MPolyline) {
printObject(((MPolyline) obj).getPoints(), format); // recursive call
} else if (obj instanceof Object[] || obj instanceof List) {
// includes multidimensional lists and arrays, e.g. int[][]
final boolean isList = obj instanceof List;
List list = null;
Object[] array = null;
int numItems;
if (isList) {
list = (List) obj;
numItems = list.size();
} else {
array = (Object[]) obj;
numItems = array.length;
} // if is list
System.out.print('[');
String divider = null;
for (int itemNum = 0; itemNum < numItems; itemNum++) {
final Object item =
(isList ? list.get(itemNum) : array[itemNum]);
if (itemNum == 0) divider = getDivider(item);
else System.out.print(divider);
printObject(item, format); // recursive call
} // for each object in the collection
System.out.print(']');
} else System.out.print(obj);
} // printObject(Object, String)

The error message:


MechData.extractArrows: arrow 3 with object number 7 has tail that originates on arrow with object number 6; this latter arrow has an unknown origin.
MechData.extractArrows: finding origin of arrow 2 with object number 6
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: point = DPoint3(-8.25, -11.65999984741211, 0.0), line = [DPoint3(0.27356451749801636, -5.380102157592773, 0.0), DPoint3(2.361952893937054, -8.997296931521465, 0.0)]
VectorMath.pointOnLine: sumAngles = 22
MechData.onArrow: point is not on arrow.
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: point = DPoint3(-8.25, -11.65999984741211, 0.0), line = [DPoint3(-9.085513567791546, -8.541820762000917, 0.0), DPoint3(-6.520300286938771, -18.129008231941995, 0.0)]
VectorMath.pointOnLine: sumAngles = 180
MechData.onArrow: point is on arrow.
MechData.extractArrows: arrow 2 with object number 6 has tail that originates on arrow with object number 7; this latter arrow originates at a branching arrow.
MechData.extractArrows: finding origin of arrow 1 with object number 5
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: point = DPoint3(-8.396491050720215, -1.4667165279388428, 0.0), line = [DPoint3(0.27356451749801636, -5.380102157592773, 0.0), DPoint3(2.361952893937054, -8.997296931521465, 0.0)]
VectorMath.pointOnLine: sumAngles = 11
MechData.onArrow: point is not on arrow.
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: point = DPoint3(-8.396491050720215, -1.4667165279388428, 0.0), line = [java.lang.StackOverflowError
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:172)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)

If I comment out the debugPrint line in pointOnLine(), I get a similar error message:


MechData.extractArrows: arrow 3 with object number 7 has tail that originates on arrow with object number 6; this latter arrow has an unknown origin.
MechData.extractArrows: finding origin of arrow 2 with object number 6
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: sumAngles = 22
MechData.onArrow: point is not on arrow.
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: sumAngles = 180
MechData.onArrow: point is on arrow.
MechData.extractArrows: arrow 2 with object number 6 has tail that originates on arrow with object number 7; this latter arrow originates at a branching arrow.
MechData.extractArrows: finding origin of arrow 1 with object number 5
MechData.onArrow: seeing if point is on arrow.
VectorMath.pointOnLine: sumAngles = 11
MechData.onArrow: point is not on arrow.
MechData.onArrow: seeing if point is on arrow.
java.lang.StackOverflowError
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)
at chemaxon.struc.graphics.MPolyline.getMidPointLocation(MPolyline.java:900)
at chemaxon.struc.graphics.MMidPoint.getFixedLocation(MMidPoint.java:206)
at chemaxon.struc.graphics.MMidPoint.getLocation(MMidPoint.java:175)
at chemaxon.struc.MPoint.getLocation(MPoint.java:123)

I've isolated the cause to the two arrows that overlap each other, but it's not clear to me why JChem should enter an infinite loop.  Any ideas on how to avoid the problem?  Is it a bug on your end that can be fixed?

ChemAxon 5433b8e56b

25-09-2012 23:16:50

Hi Bob,


I could not be able to create a reproduction code only with our API, can you show me in some way, what MechArrow is, and what does it do with the MPolyLine instance it gets in its constructor?


I have a tip that somehow the MMidPoint that is being created somehow its own parent or there are two MMidPoints that have each other as parents, and therefore an infinite recursion happens and lead to a StackOverFlowError, but this is just guessing currently, and without MechArrow currently I can not tell whether this is a bug in our side or not.


Regards,
Istvan

User 870ab5b546

27-09-2012 13:36:46

MechArrow is not very interesting.


package com.epoch.mechanisms;

import chemaxon.struc.graphics.MPolyline;
import chemaxon.struc.MPoint;
import org.apache.commons.lang.builder.HashCodeBuilder;

/** Describes a straight arrow (reaction or resonance) connecting two stages of
* a mechanism. */
public class MechArrow implements MechConstants {

/** Straight arrow from the MDoc. */
private MPolyline arrow;
/** Stage at which this straight arrow originates. */
private MechStage prevStage;
/** Stage to which this straight arrow points. */
private MechStage nextStage;
/** 0-Based index of this object in the MDoc. */
private int objectNumber;

/** Constructor. */
MechArrow() {
// intentionally empty
}

/** Constructor.
* @param newArrow the arrow from the MDoc
* @param objNum 0-based index of this object in the MDoc
*/
MechArrow(MPolyline newArrow, int objNum) {
arrow = newArrow;
objectNumber = objNum;
} // MechArrow(MPolyline, int)

/** Sets the previous and next stages connected by this straight arrow.
* @param prev the previous stage
* @param next the next stage
*/
public void setStagesPrevNext(MechStage prev, MechStage next) {
prevStage = prev;
nextStage = next;
} // setStages()

/** Sets the object number of this straight arrow in the MDoc.
* @param objNum 0-based number of this arrow in the MDoc
*/
public void setObjectNumber(int objNum) { objectNumber = objNum; }
/** Sets the arrow object from the MDoc.
* @param newArrow arrow from the MDoc
*/
public void setArrow(MPolyline newArrow) { arrow = newArrow; }

/** Gets the object number of this straight arrow in the MDoc.
* @return 0-based number of this arrow in the MDoc
*/
public int getObjectNumber() { return objectNumber; }
/** Gets whether this straight arrow is a resonance arrow.
* @return true if this arrow is a resonance arrow
*/
public boolean isResonant() { return hasWedgeAt(TAIL); }
/** Gets the arrow object from the MDoc.
* @return arrow from the MDoc
*/
public MPolyline getArrow() { return arrow; }
/** Gets either end of the arrow or the midpoint.
* @param kind which point to get
* @return a point of the arrow
*/
public MPoint getPoint(int kind) { return arrow.getPointRef(kind, null); }
/** Gets the stage at which this straight arrow originates.
* @return the previous stage
*/
public MechStage getPrevStage() { return prevStage; }
/** Gets the stage to which this straight arrow points.
* @return the next stage
*/
public MechStage getNextStage() { return nextStage; }
/** Gets whether there is a wedge at both ends of a straight arrow.
* Used to determine whether the arrow is a resonance arrow.
* @param whichEnd HEAD or TAIL
* @return true if there is a wedge
*/
public boolean hasWedgeAt(int whichEnd) {
return (arrow.getArrowLength(whichEnd) != 0);
} // hasWedgeAt(int)

/** Gets whether an arrow is of the same kind as this one.
* @param you another arrow
* @return true if the arrow equals this one
*/
public boolean equals(MechArrow you) {
return you != null && isResonant() == you.isResonant();
} // equals(MechArrow)

@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(isResonant()).toHashCode();
} // hashCode()

} // MechArrow

ChemAxon 5433b8e56b

01-10-2012 04:15:52

Hi Bob,


It was interesting to know that the getPoint method of MechArrow calls getPointRef method of the MPolyLine it gets. I was able to reproduce the problem now, but I need to dicuss it with some of my colleagues who are the expert of this area. I feel that this is a bug in our side, but I would like to be sure.


I will get back to you, after we had the discussion (hopefully today), and find out more about the expected behaviour of this part of our API.


Regards,
Istvan

User 870ab5b546

01-10-2012 13:27:10

Another point about getPoint() and getPointRef(): The API is not very clear about the differences between these methods, and it is completely silent about whether methods such as getPoints() get the original object or a copy of it.  I suggest that you make the API documentation for all these methods much more explicit.

ChemAxon 5433b8e56b

01-10-2012 15:36:26

That is an understandable request, we discussed this issue, and all of us agreed that this is a bug in our codebase, along with the fix we will make an effort to clean up the documentation, and add more information for the MObject hierarchy.


We will notify you if we have this issue fixed i hope this will be very soon.


Thanks for the report and your patientce, until that time, I have found a workaround for you, but I am not sure that is applicable in your case, since you are using the type of the points to distinguish between them. If you create a new MPoint based on the actual MMidPoint before using the setPoints method, then the getLocation works well on that new object. The problem is inside the implementation of the getLocation method of MMidPoint. Can this be a suitable workaround?


Here is my smallest code that reproduces the same issue:


MPolyline line = new MPolyline(new MPoint(new DPoint3(0.0, 0.0, 0.0)), new MPoint(new DPoint3(1.0, 1.0, 0.0)));
MPoint mp = line.getPointRef(2, null);
line.setPoints(new MPoint[]{mp, new MPoint(line.getPointRef(1, null).getLocation())});
MPoint p = line.getPointRef(0, null);
System.out.println(p.getLocation());

If I modify the second line this way:


MPoint mp = new MPoint(line.getPointRef(2, null));

then it works well and gives the expected result for me.


 


Regards,
Istvan

User 870ab5b546

04-10-2012 15:49:23

Thanks, making a copy of an MPoint before getting its location squashes the infinite recursion bug.  

ChemAxon 5433b8e56b

05-10-2012 14:02:01

I am glad to hear it, we will notify you in which version you can get rid of the workaround.


Regards,
Istvan

ChemAxon 5433b8e56b

11-10-2012 08:44:26

Hi Bob,


It seems we were able to fix the issue quickly enough to include it in 5.11.2, and 5.12 version of Marvin.


Regards,
Istvan

User 870ab5b546

02-12-2014 19:25:36

Here's a related question: Why does MPolyline.getPoint(n) give an ArrayIndexOutOfBoundsException when n = 2, whereas MPolyline.getPointRef(n, null) does not? Yes, I know it is because getPoint() returns only endpoints, whereas getPointRef() returns midpoints as well, but why is it done that way?


Also, for MRectangles, getPointRefCount() returns 8, but there are actually 9 obtainable point references, because the center point is included, and actually is obtained before the side midpoints.


<cml>
<MDocument>
<MChemicalStruct>
<molecule molID="m1">
<atomArray/>
<bondArray/>
</molecule>
</MChemicalStruct>
<MRectangle lineColor="#000000" id="o1">
<MPoint x="-5.875" y="1.0833333333333333"/>
<MPoint x="-0.875" y="1.0833333333333333"/>
<MPoint x="-0.875" y="4.625"/>
<MPoint x="-5.875" y="4.625"/>
</MRectangle>
<MPolyline headLength="0.6" headWidth="0.4" id="o2">
<MRectanglePoint rectRef="o1" pos="1"/>
<MPoint x="1.4623762795828474" y="6.323203271067888"/>
</MPolyline>
</MDocument>
</cml>

pointOnLine.jsp: rectangle 1:
point 1 (NW): DPoint3(-5.875, 1.0833333333333333, 0.0)
point 2 (NE): DPoint3(-0.875, 1.0833333333333333, 0.0)
point 3 (SE): DPoint3(-0.875, 4.625, 0.0)
point 4 (SW): DPoint3(-5.875, 4.625, 0.0)
point 5 (CENTER): DPoint3(-3.375, 1.0833333333333333, 0.0)
point 6 (N): DPoint3(-0.875, 2.8541666666666665, 0.0)
point 7 (E): DPoint3(-3.375, 4.625, 0.0)
point 8 (S): DPoint3(-5.875, 2.8541666666666665, 0.0)
point 9 (W): DPoint3(-3.375, 1.0833333333333333, 0.0)
pointOnLine.jsp: arrow 1:
point 1: DPoint3(-0.875, 1.0833333333333333, 0.0)
point 2: DPoint3(1.4623762795828474, 6.323203271067888, 0.0)
midpoint 3: DPoint3(0.2936881397914237, 3.7032683022006103, 0.0)

ChemAxon d26931946c

03-12-2014 14:14:51










bobgr wrote:

Here's a related question: Why does MPolyline.getPoint(n) give an ArrayIndexOutOfBoundsException when n = 2, whereas MPolyline.getPointRef(n, null) does not? Yes, I know it is because getPoint() returns only endpoints, whereas getPointRef() returns midpoints as well, but why is it done that way?



getPoint(n) should be consistent with getPointCount() and getPointRef(n) with getPointRefCount().


"Points" are representing the endpoints of a line while "RefPoints" are usually containing the middle point of a line too.


getPoints() are the points needed for export while "RefPoints" are calculated from the other points when needed (usually during drawing).


I admit, this part of our API is messy and hard to work with.


Also, for MRectangles, getPointRefCount() returns 8, but there are 
actually 9 obtainable point references, because the center point is
included, and actually is obtained before the side midpoints.


This seems like a bug for me, I've created a ticket for it and we will investigate it.


BRs,


Peter