/******************************************************************************* * Copyright (c) 2012-2013 CWI * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * * Arnold Lankamp - implementation * * Jurgen Vinju - implementation * * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI *******************************************************************************/ package org.rascalmpl.value.impl.primitive; import java.net.URI; import java.net.URISyntaxException; import java.util.concurrent.TimeUnit; import org.rascalmpl.value.ISourceLocation; import org.rascalmpl.value.IValue; import org.rascalmpl.value.impl.AbstractValue; import org.rascalmpl.value.type.Type; import org.rascalmpl.value.type.TypeFactory; import org.rascalmpl.value.visitors.IValueVisitor; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; /** * This is a container class for a number of implementations of ISourceLocation. Each implementation is extremely similar to the others. * except that different native types are used to store offsets, lengths, line and column indices. The goal is to use a minimum amount * of heap for each source location object, since at run-time there will be so many of them. We measured the effect of this on some real * applications and showed more than 50% improvement in memory usage. */ /*package*/ class SourceLocationValues { /*package*/ static ISourceLocation newSourceLocation(ISourceLocation loc, int offset, int length) { IURI uri = ((Incomplete)loc).uri; if (offset < 0) throw new IllegalArgumentException("offset should be positive"); if (length < 0) throw new IllegalArgumentException("length should be positive"); if (offset < Byte.MAX_VALUE && length < Byte.MAX_VALUE) { return new SourceLocationValues.ByteByte(uri, (byte) offset, (byte) length); } if (offset < Character.MAX_VALUE && length < Character.MAX_VALUE) { return new SourceLocationValues.CharChar(uri, (char) offset, (char) length); } return new SourceLocationValues.IntInt(uri, offset, length); } /*package*/ static ISourceLocation newSourceLocation(ISourceLocation loc, int offset, int length, int beginLine, int endLine, int beginCol, int endCol) { IURI uri = ((Incomplete)loc).uri; if (offset < 0) throw new IllegalArgumentException("offset should be positive"); if (length < 0) throw new IllegalArgumentException("length should be positive"); if (beginLine < 0) throw new IllegalArgumentException("beginLine should be positive"); if (beginCol < 0) throw new IllegalArgumentException("beginCol should be positive"); if (endCol < 0) throw new IllegalArgumentException("endCol should be positive"); if (endLine < beginLine) throw new IllegalArgumentException("endLine should be larger than or equal to beginLine"); if (endLine == beginLine && endCol < beginCol) throw new IllegalArgumentException("endCol should be larger than or equal to beginCol, if on the same line"); if (offset < Character.MAX_VALUE && length < Character.MAX_VALUE && beginLine < Byte.MAX_VALUE && endLine < Byte.MAX_VALUE && beginCol < Byte.MAX_VALUE && endCol < Byte.MAX_VALUE) { return new SourceLocationValues.CharCharByteByteByteByte(uri, (char) offset, (char) length, (byte) beginLine, (byte) endLine, (byte) beginCol, (byte) endCol); } else if (offset < Character.MAX_VALUE && length < Character.MAX_VALUE && beginLine < Character.MAX_VALUE && endLine < Character.MAX_VALUE && beginCol < Character.MAX_VALUE && endCol < Character.MAX_VALUE) { return new SourceLocationValues.CharCharCharCharCharChar(uri, (char) offset, (char) length, (char) beginLine, (char) endLine, (char) beginCol, (char) endCol); } else if (beginLine < Character.MAX_VALUE && endLine < Character.MAX_VALUE && beginCol < Byte.MAX_VALUE && endCol < Byte.MAX_VALUE) { return new SourceLocationValues.IntIntCharCharByteByte(uri, offset, length, (char) beginLine, (char) endLine, (byte) beginCol, (byte) endCol); } else if (beginCol < Byte.MAX_VALUE && endCol < Byte.MAX_VALUE) { return new SourceLocationValues.IntIntIntIntByteByte(uri, offset, length, beginLine, endLine, (byte) beginCol, (byte) endCol); } return new SourceLocationValues.IntIntIntIntIntInt(uri, offset, length, beginLine, endLine, beginCol, endCol); } private final static Cache<URI, ISourceLocation> locationCache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .maximumSize(1000) .build(); private final static Cache<IURI,URI> reverseLocationCache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .maximumSize(1000) .build(); /*package*/ static ISourceLocation newSourceLocation(URI uri) throws URISyntaxException { if (uri.isOpaque()) { throw new UnsupportedOperationException("Opaque URI schemes are not supported; the scheme-specific part must start with a / character."); } try { return locationCache.get(uri, u -> { try { return newSourceLocation(u.getScheme(), u.getAuthority(), u.getPath(), u.getQuery(), u.getFragment()); } catch (URISyntaxException e) { throw new RuntimeException(e); } }); } catch (RuntimeException e) { if (e.getCause() instanceof URISyntaxException) { throw (URISyntaxException)e.getCause(); } throw e; } } /*package*/ static ISourceLocation newSourceLocation(String scheme, String authority, String path, String query, String fragment) throws URISyntaxException { IURI u = SourceLocationURIValues.newURI(scheme, authority, path, query, fragment); return new SourceLocationValues.OnlyURI(u); } private abstract static class Complete extends Incomplete { private Complete(IURI uri) { super(uri); } @Override public boolean hasOffsetLength() { return true; } @Override public boolean hasLineColumn() { return true; } } private abstract static class Incomplete extends AbstractValue implements ISourceLocation { protected IURI uri; public Incomplete(IURI uri) { this.uri = uri; } @Override public Boolean hasAuthority() { return uri.hasAuthority(); } @Override public Boolean hasFragment() { return uri.hasFragment(); } @Override public Boolean hasPath() { return uri.hasPath(); } @Override public Boolean hasQuery() { return uri.hasQuery(); } @Override public String getAuthority() { return uri.getAuthority(); } @Override public String getFragment() { return uri.getFragment(); } @Override public String getPath() { return uri.getPath(); } @Override public String getQuery() { return uri.getQuery(); } @Override public String getScheme() { return uri.getScheme(); } @Override public ISourceLocation top() { return new OnlyURI(uri); } @Override public URI getURI() { return reverseLocationCache.get(uri, u -> { URI result = u.getURI(); try { // assure correct encoding, side effect of JRE's implementation of URIs result = new URI(result.toASCIIString()); } catch (URISyntaxException e) { } return result; }); } @Override public Type getType(){ return TypeFactory.getInstance().sourceLocationType(); } @Override public boolean hasLineColumn() { return false; } @Override public boolean hasOffsetLength() { return false; } @Override public int getBeginColumn() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public int getBeginLine() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public int getEndColumn() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public int getEndLine() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public int getLength() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public int getOffset() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public <T, E extends Throwable> T accept(IValueVisitor<T,E> v) throws E{ return v.visitSourceLocation(this); } @Override public boolean isEqual(IValue value){ return equals(value); } } private static class IntIntIntIntIntInt extends Complete { protected final int offset; protected final int length; protected final int beginLine; protected final int endLine; protected final int beginCol; protected final int endCol; private IntIntIntIntIntInt(IURI uri, int offset, int length, int beginLine, int endLine, int beginCol, int endCol){ super(uri); this.offset = offset; this.length = length; this.beginLine = beginLine; this.endLine = endLine; this.beginCol = beginCol; this.endCol = endCol; } @Override public Type getType(){ return TypeFactory.getInstance().sourceLocationType(); } @Override public int getBeginLine(){ return beginLine; } @Override public int getEndLine(){ return endLine; } @Override public int getBeginColumn(){ return beginCol; } @Override public int getEndColumn(){ return endCol; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= beginLine << 3; hash ^= (endLine << 23); hash ^= (beginCol << 13); hash ^= (endCol << 18); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ IntIntIntIntIntInt otherSourceLocation = (IntIntIntIntIntInt) o; return (uri.equals(otherSourceLocation.uri) && (beginLine == otherSourceLocation.beginLine) && (endLine == otherSourceLocation.endLine) && (beginCol == otherSourceLocation.beginCol) && (endCol == otherSourceLocation.endCol) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class CharCharByteByteByteByte extends Complete { protected final char offset; protected final char length; protected final byte beginLine; protected final byte endLine; protected final byte beginCol; protected final byte endCol; private CharCharByteByteByteByte(IURI uri, char offset, char length, byte beginLine, byte endLine, byte beginCol, byte endCol){ super(uri); this.offset = offset; this.length = length; this.beginLine = beginLine; this.endLine = endLine; this.beginCol = beginCol; this.endCol = endCol; } @Override public Type getType(){ return TypeFactory.getInstance().sourceLocationType(); } @Override public int getBeginLine(){ return beginLine; } @Override public int getEndLine(){ return endLine; } @Override public int getBeginColumn(){ return beginCol; } @Override public int getEndColumn(){ return endCol; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= beginLine << 3; hash ^= (endLine << 23); hash ^= (beginCol << 13); hash ^= (endCol << 18); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ CharCharByteByteByteByte otherSourceLocation = (CharCharByteByteByteByte) o; return (uri.equals(otherSourceLocation.uri) && (beginLine == otherSourceLocation.beginLine) && (endLine == otherSourceLocation.endLine) && (beginCol == otherSourceLocation.beginCol) && (endCol == otherSourceLocation.endCol) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class CharCharCharCharCharChar extends Complete { protected final char offset; protected final char length; protected final char beginLine; protected final char endLine; protected final char beginCol; protected final char endCol; private CharCharCharCharCharChar(IURI uri, char offset, char length, char beginLine, char endLine, char beginCol, char endCol){ super(uri); this.offset = offset; this.length = length; this.beginLine = beginLine; this.endLine = endLine; this.beginCol = beginCol; this.endCol = endCol; } @Override public Type getType(){ return TypeFactory.getInstance().sourceLocationType(); } @Override public int getBeginLine(){ return beginLine; } @Override public int getEndLine(){ return endLine; } @Override public int getBeginColumn(){ return beginCol; } @Override public int getEndColumn(){ return endCol; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= beginLine << 3; hash ^= (endLine << 23); hash ^= (beginCol << 13); hash ^= (endCol << 18); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ CharCharCharCharCharChar otherSourceLocation = (CharCharCharCharCharChar) o; return (uri.equals(otherSourceLocation.uri) && (beginLine == otherSourceLocation.beginLine) && (endLine == otherSourceLocation.endLine) && (beginCol == otherSourceLocation.beginCol) && (endCol == otherSourceLocation.endCol) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private final static class OnlyURI extends Incomplete { private OnlyURI(IURI uri){ super(uri); } @Override public int hashCode(){ return uri.hashCode(); } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ OnlyURI otherSourceLocation = (OnlyURI) o; return uri.equals(otherSourceLocation.uri); } return false; } @Override public ISourceLocation top() { // this is why the class is final return this; } } private static class IntIntIntIntByteByte extends Complete { protected final int offset; protected final int length; protected final int beginLine; protected final int endLine; protected final byte beginCol; protected final byte endCol; private IntIntIntIntByteByte(IURI uri, int offset, int length, int beginLine, int endLine, byte beginCol, byte endCol){ super(uri); this.offset = offset; this.length = length; this.beginLine = beginLine; this.endLine = endLine; this.beginCol = beginCol; this.endCol = endCol; } @Override public int getBeginLine(){ return beginLine; } @Override public int getEndLine(){ return endLine; } @Override public int getBeginColumn(){ return beginCol; } @Override public int getEndColumn(){ return endCol; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= beginLine << 3; hash ^= (endLine << 23); hash ^= (beginCol << 13); hash ^= (endCol << 18); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ IntIntIntIntByteByte otherSourceLocation = (IntIntIntIntByteByte) o; return (uri.equals(otherSourceLocation.uri) && (beginLine == otherSourceLocation.beginLine) && (endLine == otherSourceLocation.endLine) && (beginCol == otherSourceLocation.beginCol) && (endCol == otherSourceLocation.endCol) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class IntIntCharCharByteByte extends Complete { protected final int offset; protected final int length; protected final char beginLine; protected final char endLine; protected final byte beginCol; protected final byte endCol; private IntIntCharCharByteByte(IURI uri, int offset, int length, char beginLine, char endLine, byte beginCol, byte endCol){ super(uri); this.offset = offset; this.length = length; this.beginLine = beginLine; this.endLine = endLine; this.beginCol = beginCol; this.endCol = endCol; } @Override public int getBeginLine(){ return beginLine; } @Override public int getEndLine(){ return endLine; } @Override public int getBeginColumn(){ return beginCol; } @Override public int getEndColumn(){ return endCol; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= beginLine << 3; hash ^= (endLine << 23); hash ^= (beginCol << 13); hash ^= (endCol << 18); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ IntIntCharCharByteByte otherSourceLocation = (IntIntCharCharByteByte) o; return (uri.equals(otherSourceLocation.uri) && (beginLine == otherSourceLocation.beginLine) && (endLine == otherSourceLocation.endLine) && (beginCol == otherSourceLocation.beginCol) && (endCol == otherSourceLocation.endCol) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class ByteByte extends Incomplete { protected final byte offset; protected final byte length; private ByteByte(IURI uri, byte offset, byte length){ super(uri); this.offset = offset; this.length = length; } @Override public boolean hasOffsetLength() { return true; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ ByteByte otherSourceLocation = (ByteByte) o; return (uri.equals(otherSourceLocation.uri) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class CharChar extends Incomplete { protected final char offset; protected final char length; private CharChar(IURI uri, char offset, char length){ super(uri); this.offset = offset; this.length = length; } @Override public boolean hasOffsetLength() { return true; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ CharChar otherSourceLocation = (CharChar) o; return (uri.equals(otherSourceLocation.uri) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } private static class IntInt extends Incomplete { protected final int offset; protected final int length; private IntInt(IURI uri, int offset, int length){ super(uri); this.offset = offset; this.length = length; } @Override public boolean hasOffsetLength() { return true; } @Override public boolean hasLineColumn() { return false; } @Override public int getOffset(){ return offset; } @Override public int getLength(){ return length; } @Override public int hashCode(){ int hash = uri.hashCode(); hash ^= (offset << 8); hash ^= (length << 29); return hash; } @Override public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ IntInt otherSourceLocation = (IntInt) o; return (uri.equals(otherSourceLocation.uri) && (offset == otherSourceLocation.offset) && (length == otherSourceLocation.length)); } return false; } } }