package dtool.sourcegen; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import static melnorme.utilbox.core.CoreUtil.areEqual; import java.util.ArrayList; import java.util.ListIterator; import melnorme.lang.tooling.ast.SourceRange; import melnorme.utilbox.misc.StringUtil; public class AnnotatedSource { public final String source; public final String originalTemplatedSource; public final ArrayList<AnnotatedSource.MetadataEntry> metadata; public AnnotatedSource(String source, String originalTemplatedSource, ArrayList<MetadataEntry> arrayList) { this.source = source; this.originalTemplatedSource = originalTemplatedSource; this.metadata = arrayList; } public static class MetadataEntry { public final String name; public final String value; public final int offset; public final String sourceValue; // the parent MDE to which offset applies, or null for top-level TODO private final Object parentScopeMDE; public final boolean sourceWasIncluded; public MetadataEntry(String name, String extraValue, String associatedSource, int offset) { this(name, extraValue, associatedSource, offset, null, true); } public MetadataEntry(String name, String extraValue, String associatedSource, int offset, Object parentScopeMDE) { this(name, extraValue, associatedSource, offset, parentScopeMDE, true); } public MetadataEntry(String name, String extraValue, String associatedSource, int offset, boolean sourceWasIncluded) { this(name, extraValue, associatedSource, offset, null, sourceWasIncluded); } public MetadataEntry(String name, String extraValue, String mdSource, int offset, Object parentScopeMDE, boolean sourceWasIncluded) { this.name = name; this.value = extraValue; this.offset = offset; assertTrue(offset >= 0); this.sourceValue = mdSource; this.parentScopeMDE = parentScopeMDE; this.sourceWasIncluded = sourceWasIncluded; } public SourceRange getSourceRange() { return new SourceRange(offset, sourceValue == null ? 0 : sourceValue.length()); } public boolean isTopLevelMetadata() { return parentScopeMDE == null; } @Override public String toString() { return "["+offset+"]" + name + (sourceWasIncluded ? "" : "¤") + (value != null ? "("+value+")" : "") + (sourceValue != null ? "【"+sourceValue+"】" : ""); } } @Override public String toString() { return source + "\n--------- METADATA: ---------\n" + StringUtil.collToString(metadata, "\n"); } public MetadataEntry findMetadata(String name) { return findMetadata(name, true); } public MetadataEntry findMetadata(String name, boolean requireUnique) { MetadataEntry foundMde = null; for (MetadataEntry mde : metadata) { if(areEqual(mde.name, name)) { if(!requireUnique){ return mde; } assertTrue(foundMde == null); foundMde = mde; } } return foundMde; } public MetadataEntry findMetadata(String name, String value) { MetadataEntry foundMde = null; for (MetadataEntry mde : metadata) { if(areEqual(mde.name, name) && areEqual(mde.value, value)) { assertTrue(foundMde == null); foundMde = mde; } } return foundMde; } public static String printSourceWithMetadata(AnnotatedSource testCase) { ListIterator<MetadataEntry> mdeIter = testCase.metadata.listIterator(); StringBuffer sb = new StringBuffer(); printCaseSourceWithMetaData(testCase.source, mdeIter, 0, testCase.source.length(), null, sb); assertTrue(mdeIter.hasNext() == false); return sb.toString(); } public static void printCaseSourceWithMetaData(String source, ListIterator<MetadataEntry> mdeIter, final int startOffset, final int maxSourceOffset, MetadataEntry parentMDE, StringBuffer sb) { int offset = startOffset; while(mdeIter.hasNext()) { MetadataEntry mde = mdeIter.next(); assertTrue(offset >= 0 && maxSourceOffset >= offset); int nextOffset = mde.offset; assertTrue(nextOffset >= offset); // TODO This condition is actually buggy and will be incorrect on child MDEs of child MDEs (depth 2) // This is because AnnotatedSource class doesn't provide parent MDE info boolean mdeIsParentOfParentMDE = mde.isTopLevelMetadata() == (parentMDE == null); if(nextOffset > maxSourceOffset || !mdeIsParentOfParentMDE) { mdeIter.previous(); break; } sb.append(source.substring(offset, nextOffset)); offset = nextOffset; sb.append("#" + mde.name); if(mde.value != null) { sb.append("(" + mde.value + ")"); } if(mde.sourceValue != null) { sb.append(mde.sourceWasIncluded ? "【" : "¤【"); if(mde.sourceWasIncluded) { nextOffset += mde.sourceValue.length(); printCaseSourceWithMetaData(source, mdeIter, offset, nextOffset, parentMDE, sb); offset = nextOffset; } else { printCaseSourceWithMetaData(mde.sourceValue, mdeIter, 0, mde.sourceValue.length(), mde, sb); } sb.append("】"); } } assertTrue(offset >= 0 && maxSourceOffset >= offset); sb.append(source.substring(offset, maxSourceOffset)); } }