/* * Copyright 2003-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.smodel; import jetbrains.mps.util.InternUtil; import jetbrains.mps.util.annotation.ToRemove; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.annotations.Immutable; import java.util.UUID; public abstract class SModelId implements org.jetbrains.mps.openapi.model.SModelId { private static final String REGULAR_PREFIX = "r:"; private static final String FOREIGN_PREFIX = "f:"; public static SModelId generate() { return new RegularSModelId(UUID.randomUUID()); } public static SModelId regular(UUID uid) { return new RegularSModelId(uid); } public static SModelId regular(String suffix) { try { UUID uuid = UUID.fromString(suffix); return regular(uuid); } catch (IllegalArgumentException e) { long lower = Long.parseLong(suffix); UUID uuid = new UUID(0x0000000000004000, lower); return regular(uuid); } } public static SModelId foreign(@NotNull String id) { return new ForeignSModelId(id); } /** * See {@link jetbrains.mps.smodel.SModelId.ForeignSModelId} for details */ public static SModelId foreign(@Nullable String kind, @NotNull String id) { if (kind != null && !kind.trim().isEmpty()) { return new ForeignSModelId(kind.trim() + "#" + id); } else { return new ForeignSModelId(id); } } /** * @deprecated moduleId shall not be part of model id. Although it's possible that intention was to make it 'globally unique' (as it superclass suggests), * usage pattern tells us they were not deemed global (there's always module id when a model reference with foreign id is created). And even global id shall * not use set of strings concatenated with '#' to describe complex data structure. */ @Deprecated @ToRemove(version = 3.4) public static SModelId foreign(String kind, String moduleId, String id) { if (moduleId == null || moduleId.length() == 0) { return new ForeignSModelId(kind + "#" + id); } return new ForeignSModelId(kind + "#" + moduleId + "#" + id); } /** * @deprecated this method doesn't support {@link org.jetbrains.mps.openapi.persistence.SModelIdFactory}, * use {@link org.jetbrains.mps.openapi.persistence.PersistenceFacade#createModelId(String)} instead. */ @Deprecated @ToRemove(version = 3.3) public static SModelId fromString(String id) { if (id.startsWith(REGULAR_PREFIX)) { String suffix = id.substring(REGULAR_PREFIX.length()); return regular(suffix); } if (id.startsWith(FOREIGN_PREFIX)) { String suffix = id.substring(FOREIGN_PREFIX.length()); return foreign(suffix); } if (id.startsWith(RelativePathSModelId.TYPE + ":")) { return new RelativePathSModelId(id.substring(1 + RelativePathSModelId.TYPE.length())); } throw new IllegalArgumentException("wrong id " + id); } private SModelId() { } /** * @deprecated model id is immutable, what's the point to make a copy? */ @Deprecated @ToRemove(version = 3.4) public SModelId getCopy() { return fromString(toString()); } @Override public boolean isGloballyUnique() { return true; } @Override public String getModelName() { return null; } @Immutable public final static class RegularSModelId extends SModelId { public static final String TYPE = "r"; private final UUID myId; /*package*/ RegularSModelId(UUID id) { myId = id; } public UUID getId() { return myId; } public boolean equals(Object obj) { if (!(obj instanceof RegularSModelId)) return false; return ((RegularSModelId) obj).myId.equals(myId); } public int hashCode() { return myId.hashCode(); } @Override public String getType() { return TYPE; } public String toString() { return REGULAR_PREFIX + myId; } } /** * Model identity based on plain string, with optional kind part up to first hash ('#') sign (i.e. "[kind#]identity"). * Almost any string could be used for identity, provided it's unique within a repository. * Although there's no known restrictions about string except uniqueness at the moment, it's advised not to stretch this freedom too much. * Primary difference with {@link jetbrains.mps.smodel.SModelId.ModelNameSModelId} is that identity is not treated as model name * * IMPORTANT: it's advised not to use this kind of model id and leave it for legacy code (e.g. VCS that reads model in old persistence format). * This one has misguiding name (foreign to what?), mandates globally uniqueness while doesn't help to achieve one. * Consider use of {@link jetbrains.mps.smodel.SModelId.IntegerSModelId} if you need simple model id. */ @Immutable public final static class ForeignSModelId extends SModelId { public static final String TYPE = "f"; private final String myId; /*package*/ ForeignSModelId(String id) { myId = InternUtil.intern(id); } /** * @return never <code>null</code> */ public String getId() { return myId; } /** * @return optional part of the identity string, up to first '#', excluding; empty string if no kind part found */ public String getKind() { // What's the point of this ForeignSModelId then if we don't expose 'kind', provided we've specified one at creation time? // It could be plain AnyStringModelId then. int x = myId.indexOf('#'); return x == -1 ? "" : myId.substring(0, x); } public boolean equals(Object obj) { if (!(obj instanceof ForeignSModelId)) return false; return ((ForeignSModelId) obj).myId.equals(myId); } public int hashCode() { return myId.hashCode(); } @Override public String getType() { return TYPE; } public String toString() { return FOREIGN_PREFIX + myId; } } @Immutable public final static class ModelNameSModelId extends SModelId { public static final String TYPE = "m"; private final String myModelName; public ModelNameSModelId(String modelName) { super(); myModelName = modelName; } @Override public String getModelName() { return myModelName; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return myModelName.equals(((ModelNameSModelId) o).myModelName); } @Override public int hashCode() { return myModelName.hashCode(); } @Override public String getType() { return TYPE; } public String toString() { return TYPE + ":" + myModelName; } } @Immutable public final static class RelativePathSModelId extends SModelId { public static final String TYPE = "path"; private final String myPath; public RelativePathSModelId(String path) { super(); myPath = path; } @Override public String getModelName() { return myPath; } public String getFileName() { int i = myPath.lastIndexOf('/'); if (i >= 0 && i + 1 < myPath.length()) { return myPath.substring(i + 1); } return myPath; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; return myPath.equals(((RelativePathSModelId) o).myPath); } @Override public int hashCode() { return myPath.hashCode(); } @Override public String getType() { return TYPE; } public String toString() { return TYPE + ":" + myPath; } } /** * Integer-backed, module-private model identity. * MPS reserves values in range [0x0F000000..0xFFFFFFFF] for own uses, you're free to use lower range. */ @Immutable public static final class IntegerSModelId extends SModelId { public static final String TYPE = "i"; private final int myValue; public IntegerSModelId(int value) { myValue = value; } public int getValue() { return myValue; } @Override public boolean isGloballyUnique() { return false; } @Override public int hashCode() { return myValue; } @Override public boolean equals(Object obj) { return obj instanceof IntegerSModelId && ((IntegerSModelId) obj).myValue == myValue; } @Override public String getType() { return TYPE; } @Override public String toString() { return String.format("%s:%04x", TYPE, myValue); } public static IntegerSModelId parse(String cs) throws IllegalArgumentException { return new IntegerSModelId(Integer.parseInt(cs, 16)); } } }