/* * Copyright 2003-2014 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.persistence.def.v9; import jetbrains.mps.persistence.registry.IdInfoRegistry; import jetbrains.mps.smodel.JavaFriendlyBase64; import jetbrains.mps.smodel.SNodeId.Foreign; import jetbrains.mps.smodel.SNodeId.Regular; import jetbrains.mps.smodel.StaticReference; import jetbrains.mps.smodel.adapter.ids.SConceptId; import jetbrains.mps.smodel.adapter.ids.SContainmentLinkId; import jetbrains.mps.smodel.adapter.ids.SLanguageId; import jetbrains.mps.smodel.adapter.ids.SPropertyId; import jetbrains.mps.smodel.adapter.ids.SReferenceLinkId; import jetbrains.mps.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SModelReference; import org.jetbrains.mps.openapi.model.SNodeId; import org.jetbrains.mps.openapi.model.SReference; import org.jetbrains.mps.openapi.module.SModuleReference; import org.jetbrains.mps.openapi.persistence.PersistenceFacade; /** * Intention is to keep all serialize/de-serialize code in a single place. * <p/> * FIXME this class is public merely for the sake of GoToNodeById action. Once this encoder is part of persistence API, action shall use API, not this class * <p/> * This class is not thread-safe, uses internal buffers to save memory on (de-)serialize, do not share it between thread (although unlikely to happen as * persistence demands single thread access). */ public final class IdEncoder implements IdInfoRegistry.IndexEncoder { // separator for import in serialized reference target private static final char REF_TARGET_IMPORT_SEPARATOR = ':'; private static final String DYNAMIC_REFERENCE_ID = "^"; private final JavaFriendlyBase64 myBase64 = new JavaFriendlyBase64(); public IdEncoder() { } public String toText(SLanguageId langId) { return langId.serialize(); } public SLanguageId parseLanguageId(String text) { return SLanguageId.deserialize(text); } public String toText(SConceptId conceptId) { return Long.toString(conceptId.getIdValue()); } public SConceptId parseConceptId(SLanguageId lang, String text) { return new SConceptId(lang, Long.parseLong(text)); } public String toText(SPropertyId propertyId) { return Long.toString(propertyId.getIdValue()); } public SPropertyId parsePropertyId(SConceptId concept, String text) { return new SPropertyId(concept, Long.parseLong(text)); } public String toText(SReferenceLinkId linkId) { return Long.toString(linkId.getIdValue()); } public SReferenceLinkId parseAssociation(SConceptId concept, String text) { return new SReferenceLinkId(concept, Long.parseLong(text)); } public String toText(SContainmentLinkId linkId) { return Long.toString(linkId.getIdValue()); } public SContainmentLinkId parseAggregation(SConceptId concept, String text) { return new SContainmentLinkId(concept, Long.parseLong(text)); } public String toText(SNodeId nodeId) { if (nodeId instanceof Regular) { final long v = ((Regular) nodeId).getId(); return myBase64.toString(v); } // fall-through return nodeId.toString(); } public SNodeId parseNodeId(String text) throws EncodingException { try { if (!text.startsWith(Foreign.ID_PREFIX)) { long v = myBase64.parseLong(text); return new Regular(v); } // fall-through return jetbrains.mps.smodel.SNodeId.fromString(text); } catch (IllegalArgumentException ex) { throw new EncodingException(ex.getMessage()); } } public String toText(SModelReference mr) { return PersistenceFacade.getInstance().asString(mr); } public SModelReference parseModelReference(String text) { return PersistenceFacade.getInstance().createModelReference(text); } public String toText(SModuleReference ref) { // return PersistenceFacade.getInstance().asString(ref); FIXME add counterpart for createModuleReference return ref.toString(); } public SModuleReference parseModuleReference(String text) { return PersistenceFacade.getInstance().createModuleReference(text); } public String toTextLocal(SReference ref) { String target; if (ref instanceof StaticReference) { final SNodeId targetNodeId = ref.getTargetNodeId(); if (targetNodeId == null) { target = DYNAMIC_REFERENCE_ID; } else { target = toText(targetNodeId); } } else { target = DYNAMIC_REFERENCE_ID; } return target; } /** * Local references are saved in a form of serialized node id, or '^' for dynamic references. * External references are prefixed with import index and ':'. * <p/> * NOTE, the way import index and nodeId value are serialized is expected to never include ':' separator char */ public String toTextExternal(@NotNull ImportsHelper imports, @NotNull SReference ref) { String target = toTextLocal(ref); SModelReference targetModel = ref.getTargetSModelReference(); if (targetModel == null) { return REF_TARGET_IMPORT_SEPARATOR + target; } String index = imports.getIndex(targetModel); assert index != null : "model " + targetModel + " not found in index"; return index + REF_TARGET_IMPORT_SEPARATOR + target; } @Nullable public SNodeId parseLocalNodeReference(String text) { if (DYNAMIC_REFERENCE_ID.equals(text)) { return null; } try { return parseNodeId(text); } catch (EncodingException e) { throw new IllegalArgumentException(e); } } public Pair<SModelReference, SNodeId> parseExternalNodeReference(ImportsHelper imports, String referenceTarget) { int separatorIndex = referenceTarget.indexOf(REF_TARGET_IMPORT_SEPARATOR); assert separatorIndex >= 0; final SModelReference modelRef = separatorIndex == 0 ? null : imports.getModelReference(referenceTarget.substring(0, separatorIndex)); SNodeId nodeId = parseLocalNodeReference(referenceTarget.substring(separatorIndex + 1, referenceTarget.length())); return new Pair<SModelReference, SNodeId>(modelRef, nodeId); } /** * Dedicated alternative of the {@link #parseExternalNodeReference(String)} that cares about target node id only, for indexing purposes, * see {@link jetbrains.mps.smodel.persistence.def.v9.Indexer9} */ @Nullable SNodeId parseExternalNodeReference(String referenceTarget) { int separatorIndex = referenceTarget.indexOf(REF_TARGET_IMPORT_SEPARATOR); assert separatorIndex >= 0; return parseLocalNodeReference(referenceTarget.substring(separatorIndex + 1, referenceTarget.length())); } @Override public String index(int key) { return myBase64.indexValue(key); } public static class EncodingException extends Exception { public EncodingException(String message) { super(message); } } }