Пример #1
0
 @NonNull
 public IAndroidTarget[] getMissingTargets() {
   synchronized (mLocalPackages) {
     if (mCachedMissingTargets == null) {
       Map<MissingTarget, MissingTarget> result = Maps.newHashMap();
       Set<ISystemImage> seen = Sets.newHashSet();
       for (IAndroidTarget target : getTargets()) {
         Collections.addAll(seen, target.getSystemImages());
       }
       for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_ADDON_SYS_IMAGE)) {
         LocalAddonSysImgPkgInfo info = (LocalAddonSysImgPkgInfo) local;
         ISystemImage image = info.getSystemImage();
         if (!seen.contains(image)) {
           addOrphanedSystemImage(image, info.getDesc(), result);
         }
       }
       for (LocalPkgInfo local : getPkgsInfos(PkgType.PKG_SYS_IMAGE)) {
         LocalSysImgPkgInfo info = (LocalSysImgPkgInfo) local;
         ISystemImage image = info.getSystemImage();
         if (!seen.contains(image)) {
           addOrphanedSystemImage(image, info.getDesc(), result);
         }
       }
       mCachedMissingTargets = result.keySet();
     }
     return mCachedMissingTargets.toArray(new IAndroidTarget[mCachedMissingTargets.size()]);
   }
 }
Пример #2
0
 public NBTStorage(final File file, final String name) {
   this.root = (Map<String, Tag>) Maps.newHashMap();
   this.file = file;
   if (!this.file.exists()) {
     this.create();
   }
   this.name = name;
 }
  private void writeDelegateMethods(
      final ClassVisitor visitor,
      final Type generatedType,
      StructSchema<?> delegateSchema,
      Set<Class<?>> typesToDelegate) {
    Class<?> delegateClass = delegateSchema.getType().getConcreteClass();
    Type delegateType = Type.getType(delegateClass);
    Map<Equivalence.Wrapper<Method>, Map<Class<?>, Method>> methodsToDelegate = Maps.newHashMap();
    for (Class<?> typeToDelegate : typesToDelegate) {
      for (Method methodToDelegate : typeToDelegate.getMethods()) {
        if (ModelSchemaUtils.isIgnoredMethod(methodToDelegate)) {
          continue;
        }
        Equivalence.Wrapper<Method> methodKey = METHOD_EQUIVALENCE.wrap(methodToDelegate);
        Map<Class<?>, Method> methodsByReturnType = methodsToDelegate.get(methodKey);
        if (methodsByReturnType == null) {
          methodsByReturnType = Maps.newHashMap();
          methodsToDelegate.put(methodKey, methodsByReturnType);
        }
        methodsByReturnType.put(methodToDelegate.getReturnType(), methodToDelegate);
      }
    }
    Set<Equivalence.Wrapper<Method>> delegateMethodKeys =
        ImmutableSet.copyOf(
            Iterables.transform(
                Arrays.asList(delegateClass.getMethods()),
                new Function<Method, Equivalence.Wrapper<Method>>() {
                  @Override
                  public Equivalence.Wrapper<Method> apply(Method method) {
                    return METHOD_EQUIVALENCE.wrap(method);
                  }
                }));
    for (Map.Entry<Equivalence.Wrapper<Method>, Map<Class<?>, Method>> entry :
        methodsToDelegate.entrySet()) {
      Equivalence.Wrapper<Method> methodKey = entry.getKey();
      if (!delegateMethodKeys.contains(methodKey)) {
        continue;
      }

      Map<Class<?>, Method> methodsByReturnType = entry.getValue();
      for (Method methodToDelegate : methodsByReturnType.values()) {
        writeDelegatedMethod(visitor, generatedType, delegateType, methodToDelegate);
      }
    }
  }
Пример #4
0
 private static Map<ClassDescriptor, JetType> getSuperclassToSupertypeMap(
     ClassDescriptor containingClass) {
   Map<ClassDescriptor, JetType> superclassToSupertype = Maps.newHashMap();
   for (JetType supertype : TypeUtils.getAllSupertypes(containingClass.getDefaultType())) {
     ClassifierDescriptor superclass = supertype.getConstructor().getDeclarationDescriptor();
     assert superclass instanceof ClassDescriptor;
     superclassToSupertype.put((ClassDescriptor) superclass, supertype);
   }
   return superclassToSupertype;
 }
    public List<Vertex> getProductAssociations(Vertex v) {
      Map<Vertex, Edge> adjacent = Maps.newHashMap(graph.row(v));
      adjacent.putAll(graph.column(v));

      return adjacent
          .entrySet()
          .stream()
          .sorted((e1, e2) -> e2.getValue().weight - e1.getValue().weight)
          .map(Map.Entry::getKey)
          .collect(Collectors.toList());
    }
Пример #6
0
/**
 * Rest配置中心
 *
 * @author Peter Zhang
 */
public class RestConfigurations {

  private static Map<String, RestServiceConfiguration> CONFIG_FOR_NAME = Maps.newHashMap();

  private static Map<String, RestServiceConfiguration> CONFIG_FOR_SERVICE_NAME = Maps.newHashMap();

  private static Map<String, RestServiceConfiguration> CONFIG_FOR_ENV = Maps.newHashMap();

  private static RestServiceConfiguration DEFAULT_CONFIG;

  /**
   * 注册配置
   *
   * @param configuration Rest服务基础配置
   */
  public static void register(RestServiceConfiguration configuration) {

    CONFIG_FOR_NAME.put(configuration.getConfigurationName(), configuration);
    CONFIG_FOR_SERVICE_NAME.put(configuration.getCgiServiceName(), configuration);
    CONFIG_FOR_ENV.put(configuration.getEnvironment(), configuration);
    DEFAULT_CONFIG = configuration;
  }

  /** 获取默认配置对象 */
  public static RestServiceConfiguration getDefaultConfiguration() {

    return DEFAULT_CONFIG;
  }

  /**
   * 根据环境获取配置
   *
   * @param environment 环境
   */
  public static RestServiceConfiguration getConfigByEnvironment(String environment) {

    return CONFIG_FOR_ENV.get(environment);
  }
}
 /**
  * 按照对应关系对 aitID:threadInfo-> 1:n 对N从大到小排序 处理MulitMap中的数据,key:value->1:n 取出<key, n>
  * 对n降序排序后,取得序列后的List<Map.Entry<key, n>>
  *
  * @return
  */
 public List<Map.Entry<String, Integer>> getOrderList(Multimap<String, ThreadInfo> w_IdMap) {
   Set<String> keys = w_IdMap.keySet();
   Map<String, Integer> w_IdMappingThread = Maps.newHashMap();
   for (String key : keys) {
     Collection<ThreadInfo> values = w_IdMap.get(key);
     w_IdMappingThread.put(key, values.size());
   }
   List<Map.Entry<String, Integer>> orderList =
       new ArrayList<Map.Entry<String, Integer>>(w_IdMappingThread.entrySet());
   Collections.sort(
       orderList,
       new Comparator<Map.Entry<String, Integer>>() {
         @Override
         public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
           return o2.getValue().compareTo(o1.getValue());
         }
       });
   return orderList;
 }
Пример #8
0
  /**
   * Prepares a temporary attributes map for serialization purposes. Includes only the requested
   * elements in the map.
   */
  private Map<String, Object> prepareAttributesForSerialization(
      boolean saveDocuments, boolean saveClusters, boolean saveOtherAttributes) {
    final Map<String, Object> tempAttributes = Maps.newHashMap();

    if (saveOtherAttributes) {
      tempAttributes.putAll(attributes);
      tempAttributes.remove(AttributeNames.DOCUMENTS);
      tempAttributes.remove(AttributeNames.CLUSTERS);
    } else {
      tempAttributes.put(AttributeNames.QUERY, attributes.get(AttributeNames.QUERY));
    }

    if (saveDocuments) {
      tempAttributes.put(AttributeNames.DOCUMENTS, getDocuments());
    }

    if (saveClusters) {
      tempAttributes.put(AttributeNames.CLUSTERS, getClusters());
    }

    return tempAttributes;
  }
Пример #9
0
 @Override
 public Map<String, Object> getValuesDeep() {
   final Tag tag = this.findLastTag(this.path, false);
   if (!(tag instanceof CompoundTag)) {
     return Collections.emptyMap();
   }
   final Queue<Node> node =
       new ArrayDeque<Node>(
           (Collection<? extends Node>) ImmutableList.of((Object) new Node(tag)));
   final Map<String, Object> values = (Map<String, Object>) Maps.newHashMap();
   while (!node.isEmpty()) {
     final Node root = node.poll();
     for (final Map.Entry<String, Tag> entry : root.values.entrySet()) {
       final String key = this.createRelativeKey(root.parent, entry.getKey());
       if (entry.getValue() instanceof CompoundTag) {
         node.add(new Node(key, entry.getValue()));
       } else {
         values.put(key, entry.getValue().getValue());
       }
     }
   }
   return values;
 }
Пример #10
0
  /** Transfers document and cluster lists to the attributes map after deserialization. */
  @Commit
  private void afterDeserialization() throws Exception {
    if (otherAttributesForSerialization != null) {
      attributes = SimpleXmlWrappers.unwrap(otherAttributesForSerialization);
    }

    attributesView = Collections.unmodifiableMap(attributes);

    attributes.put(AttributeNames.QUERY, query != null ? query.trim() : null);
    attributes.put(AttributeNames.DOCUMENTS, documents);
    attributes.put(AttributeNames.CLUSTERS, clusters);

    // Convert document ids to the actual references
    if (clusters != null && documents != null) {
      final Map<String, Document> documentsById = Maps.newHashMap();
      for (Document document : documents) {
        documentsById.put(document.getStringId(), document);
      }

      for (Cluster cluster : clusters) {
        documentIdToReference(cluster, documentsById);
      }
    }
  }
Пример #11
0
public class PseudocodeImpl implements Pseudocode {

  public class PseudocodeLabel implements Label {
    private final String name;
    private final String comment;
    private Integer targetInstructionIndex;

    private PseudocodeLabel(@NotNull String name, @Nullable String comment) {
      this.name = name;
      this.comment = comment;
    }

    @NotNull
    @Override
    public String getName() {
      return name;
    }

    @Override
    public String toString() {
      return comment == null ? name : (name + " [" + comment + "]");
    }

    public Integer getTargetInstructionIndex() {
      return targetInstructionIndex;
    }

    public void setTargetInstructionIndex(int targetInstructionIndex) {
      this.targetInstructionIndex = targetInstructionIndex;
    }

    @Nullable
    private List<Instruction> resolve() {
      assert targetInstructionIndex != null;
      return mutableInstructionList.subList(
          getTargetInstructionIndex(), mutableInstructionList.size());
    }

    public Instruction resolveToInstruction() {
      assert targetInstructionIndex != null;
      return mutableInstructionList.get(targetInstructionIndex);
    }

    public PseudocodeLabel copy(int newLabelIndex) {
      return new PseudocodeLabel("L" + newLabelIndex, "copy of " + name + ", " + comment);
    }

    public PseudocodeImpl getPseudocode() {
      return PseudocodeImpl.this;
    }
  }

  private final List<Instruction> mutableInstructionList = new ArrayList<Instruction>();
  private final List<Instruction> instructions = new ArrayList<Instruction>();

  private final BidirectionalMap<KtElement, PseudoValue> elementsToValues =
      new BidirectionalMap<KtElement, PseudoValue>();

  private final Map<PseudoValue, List<Instruction>> valueUsages = Maps.newHashMap();
  private final Map<PseudoValue, Set<PseudoValue>> mergedValues = Maps.newHashMap();
  private final Set<Instruction> sideEffectFree = Sets.newHashSet();

  private Pseudocode parent = null;
  private Set<LocalFunctionDeclarationInstruction> localDeclarations = null;
  // todo getters
  private final Map<KtElement, Instruction> representativeInstructions =
      new HashMap<KtElement, Instruction>();

  private final List<PseudocodeLabel> labels = new ArrayList<PseudocodeLabel>();

  private final KtElement correspondingElement;
  private SubroutineExitInstruction exitInstruction;
  private SubroutineSinkInstruction sinkInstruction;
  private SubroutineExitInstruction errorInstruction;
  private boolean postPrecessed = false;

  public PseudocodeImpl(KtElement correspondingElement) {
    this.correspondingElement = correspondingElement;
  }

  @NotNull
  @Override
  public KtElement getCorrespondingElement() {
    return correspondingElement;
  }

  @NotNull
  @Override
  public Set<LocalFunctionDeclarationInstruction> getLocalDeclarations() {
    if (localDeclarations == null) {
      localDeclarations = getLocalDeclarations(this);
    }
    return localDeclarations;
  }

  @NotNull
  private static Set<LocalFunctionDeclarationInstruction> getLocalDeclarations(
      @NotNull Pseudocode pseudocode) {
    Set<LocalFunctionDeclarationInstruction> localDeclarations = Sets.newLinkedHashSet();
    for (Instruction instruction : ((PseudocodeImpl) pseudocode).mutableInstructionList) {
      if (instruction instanceof LocalFunctionDeclarationInstruction) {
        localDeclarations.add((LocalFunctionDeclarationInstruction) instruction);
        localDeclarations.addAll(
            getLocalDeclarations(((LocalFunctionDeclarationInstruction) instruction).getBody()));
      }
    }
    return localDeclarations;
  }

  @Override
  @Nullable
  public Pseudocode getParent() {
    return parent;
  }

  private void setParent(Pseudocode parent) {
    this.parent = parent;
  }

  @NotNull
  public Pseudocode getRootPseudocode() {
    Pseudocode parent = getParent();
    while (parent != null) {
      if (parent.getParent() == null) return parent;
      parent = parent.getParent();
    }
    return this;
  }

  /*package*/ PseudocodeLabel createLabel(@NotNull String name, @Nullable String comment) {
    PseudocodeLabel label = new PseudocodeLabel(name, comment);
    labels.add(label);
    return label;
  }

  @Override
  @NotNull
  public List<Instruction> getInstructions() {
    return instructions;
  }

  @NotNull
  @Override
  public List<Instruction> getReversedInstructions() {
    LinkedHashSet<Instruction> traversedInstructions = Sets.newLinkedHashSet();
    PseudocodeTraverserKt.traverseFollowingInstructions(
        sinkInstruction, traversedInstructions, BACKWARD, null);
    if (traversedInstructions.size() < instructions.size()) {
      List<Instruction> simplyReversedInstructions = Lists.newArrayList(instructions);
      Collections.reverse(simplyReversedInstructions);
      for (Instruction instruction : simplyReversedInstructions) {
        if (!traversedInstructions.contains(instruction)) {
          PseudocodeTraverserKt.traverseFollowingInstructions(
              instruction, traversedInstructions, BACKWARD, null);
        }
      }
    }
    return Lists.newArrayList(traversedInstructions);
  }

  @Override
  @NotNull
  public List<Instruction> getInstructionsIncludingDeadCode() {
    return mutableInstructionList;
  }

  // for tests only
  @NotNull
  public List<PseudocodeLabel> getLabels() {
    return labels;
  }

  /*package*/ void addExitInstruction(SubroutineExitInstruction exitInstruction) {
    addInstruction(exitInstruction);
    assert this.exitInstruction == null;
    this.exitInstruction = exitInstruction;
  }

  /*package*/ void addSinkInstruction(SubroutineSinkInstruction sinkInstruction) {
    addInstruction(sinkInstruction);
    assert this.sinkInstruction == null;
    this.sinkInstruction = sinkInstruction;
  }

  /*package*/ void addErrorInstruction(SubroutineExitInstruction errorInstruction) {
    addInstruction(errorInstruction);
    assert this.errorInstruction == null;
    this.errorInstruction = errorInstruction;
  }

  /*package*/ void addInstruction(Instruction instruction) {
    mutableInstructionList.add(instruction);
    instruction.setOwner(this);

    if (instruction instanceof KtElementInstruction) {
      KtElementInstruction elementInstruction = (KtElementInstruction) instruction;
      representativeInstructions.put(elementInstruction.getElement(), instruction);
    }

    if (instruction instanceof MergeInstruction) {
      addMergedValues((MergeInstruction) instruction);
    }

    for (PseudoValue inputValue : instruction.getInputValues()) {
      addValueUsage(inputValue, instruction);
      for (PseudoValue mergedValue : getMergedValues(inputValue)) {
        addValueUsage(mergedValue, instruction);
      }
    }
    if (PseudocodeUtilsKt.calcSideEffectFree(instruction)) {
      sideEffectFree.add(instruction);
    }
  }

  @Override
  @NotNull
  public SubroutineExitInstruction getExitInstruction() {
    return exitInstruction;
  }

  @Override
  @NotNull
  public SubroutineSinkInstruction getSinkInstruction() {
    return sinkInstruction;
  }

  @Override
  @NotNull
  public SubroutineEnterInstruction getEnterInstruction() {
    return (SubroutineEnterInstruction) mutableInstructionList.get(0);
  }

  @Nullable
  @Override
  public PseudoValue getElementValue(@Nullable KtElement element) {
    return elementsToValues.get(element);
  }

  @NotNull
  @Override
  public List<? extends KtElement> getValueElements(@Nullable PseudoValue value) {
    List<? extends KtElement> result = elementsToValues.getKeysByValue(value);
    return result != null ? result : Collections.<KtElement>emptyList();
  }

  @NotNull
  @Override
  public List<? extends Instruction> getUsages(@Nullable PseudoValue value) {
    List<? extends Instruction> result = valueUsages.get(value);
    return result != null ? result : Collections.<Instruction>emptyList();
  }

  @Override
  public boolean isSideEffectFree(@NotNull Instruction instruction) {
    return sideEffectFree.contains(instruction);
  }

  /*package*/ void bindElementToValue(@NotNull KtElement element, @NotNull PseudoValue value) {
    elementsToValues.put(element, value);
  }

  /*package*/ void bindLabel(Label label) {
    ((PseudocodeLabel) label).setTargetInstructionIndex(mutableInstructionList.size());
  }

  private Set<PseudoValue> getMergedValues(@NotNull PseudoValue value) {
    Set<PseudoValue> result = mergedValues.get(value);
    return result != null ? result : Collections.<PseudoValue>emptySet();
  }

  private void addMergedValues(@NotNull MergeInstruction instruction) {
    Set<PseudoValue> result = new LinkedHashSet<PseudoValue>();
    for (PseudoValue value : instruction.getInputValues()) {
      result.addAll(getMergedValues(value));
      result.add(value);
    }
    mergedValues.put(instruction.getOutputValue(), result);
  }

  private void addValueUsage(PseudoValue value, Instruction usage) {
    if (usage instanceof MergeInstruction) return;
    MapsKt.getOrPut(
            valueUsages,
            value,
            new Function0<List<Instruction>>() {
              @Override
              public List<Instruction> invoke() {
                return Lists.newArrayList();
              }
            })
        .add(usage);
  }

  public void postProcess() {
    if (postPrecessed) return;
    postPrecessed = true;
    errorInstruction.setSink(getSinkInstruction());
    exitInstruction.setSink(getSinkInstruction());
    int index = 0;
    for (Instruction instruction : mutableInstructionList) {
      // recursively invokes 'postProcess' for local declarations
      processInstruction(instruction, index);
      index++;
    }
    if (getParent() != null) return;

    // Collecting reachable instructions should be done after processing all instructions
    // (including instructions in local declarations) to avoid being in incomplete state.
    collectAndCacheReachableInstructions();
    for (LocalFunctionDeclarationInstruction localFunctionDeclarationInstruction :
        getLocalDeclarations()) {
      ((PseudocodeImpl) localFunctionDeclarationInstruction.getBody())
          .collectAndCacheReachableInstructions();
    }
  }

  private void collectAndCacheReachableInstructions() {
    Set<Instruction> reachableInstructions = collectReachableInstructions();
    for (Instruction instruction : mutableInstructionList) {
      if (reachableInstructions.contains(instruction)) {
        instructions.add(instruction);
      }
    }
    markDeadInstructions();
  }

  private void processInstruction(Instruction instruction, final int currentPosition) {
    instruction.accept(
        new InstructionVisitor() {
          @Override
          public void visitInstructionWithNext(@NotNull InstructionWithNext instruction) {
            instruction.setNext(getNextPosition(currentPosition));
          }

          @Override
          public void visitJump(@NotNull AbstractJumpInstruction instruction) {
            instruction.setResolvedTarget(getJumpTarget(instruction.getTargetLabel()));
          }

          @Override
          public void visitNondeterministicJump(
              @NotNull NondeterministicJumpInstruction instruction) {
            instruction.setNext(getNextPosition(currentPosition));
            List<Label> targetLabels = instruction.getTargetLabels();
            for (Label targetLabel : targetLabels) {
              instruction.setResolvedTarget(targetLabel, getJumpTarget(targetLabel));
            }
          }

          @Override
          public void visitConditionalJump(@NotNull ConditionalJumpInstruction instruction) {
            Instruction nextInstruction = getNextPosition(currentPosition);
            Instruction jumpTarget = getJumpTarget(instruction.getTargetLabel());
            if (instruction.getOnTrue()) {
              instruction.setNextOnFalse(nextInstruction);
              instruction.setNextOnTrue(jumpTarget);
            } else {
              instruction.setNextOnFalse(jumpTarget);
              instruction.setNextOnTrue(nextInstruction);
            }
            visitJump(instruction);
          }

          @Override
          public void visitLocalFunctionDeclarationInstruction(
              @NotNull LocalFunctionDeclarationInstruction instruction) {
            PseudocodeImpl body = (PseudocodeImpl) instruction.getBody();
            body.setParent(PseudocodeImpl.this);
            body.postProcess();
            instruction.setNext(getSinkInstruction());
          }

          @Override
          public void visitSubroutineExit(@NotNull SubroutineExitInstruction instruction) {
            // Nothing
          }

          @Override
          public void visitSubroutineSink(@NotNull SubroutineSinkInstruction instruction) {
            // Nothing
          }

          @Override
          public void visitInstruction(@NotNull Instruction instruction) {
            throw new UnsupportedOperationException(instruction.toString());
          }
        });
  }

  private Set<Instruction> collectReachableInstructions() {
    Set<Instruction> visited = Sets.newHashSet();
    PseudocodeTraverserKt.traverseFollowingInstructions(
        getEnterInstruction(), visited, FORWARD, null);
    if (!visited.contains(getExitInstruction())) {
      visited.add(getExitInstruction());
    }
    if (!visited.contains(errorInstruction)) {
      visited.add(errorInstruction);
    }
    if (!visited.contains(getSinkInstruction())) {
      visited.add(getSinkInstruction());
    }
    return visited;
  }

  private void markDeadInstructions() {
    Set<Instruction> instructionSet = Sets.newHashSet(instructions);
    for (Instruction instruction : mutableInstructionList) {
      if (!instructionSet.contains(instruction)) {
        ((InstructionImpl) instruction).setMarkedAsDead(true);
        for (Instruction nextInstruction : instruction.getNextInstructions()) {
          nextInstruction.getPreviousInstructions().remove(instruction);
        }
      }
    }
  }

  @NotNull
  private Instruction getJumpTarget(@NotNull Label targetLabel) {
    return ((PseudocodeLabel) targetLabel).resolveToInstruction();
  }

  @NotNull
  private Instruction getNextPosition(int currentPosition) {
    int targetPosition = currentPosition + 1;
    assert targetPosition < mutableInstructionList.size() : currentPosition;
    return mutableInstructionList.get(targetPosition);
  }

  @Override
  public PseudocodeImpl copy() {
    PseudocodeImpl result = new PseudocodeImpl(correspondingElement);
    result.repeatWhole(this);
    return result;
  }

  private void repeatWhole(@NotNull PseudocodeImpl originalPseudocode) {
    repeatInternal(originalPseudocode, null, null, 0);
    parent = originalPseudocode.parent;
  }

  public int repeatPart(@NotNull Label startLabel, @NotNull Label finishLabel, int labelCount) {
    return repeatInternal(
        ((PseudocodeLabel) startLabel).getPseudocode(), startLabel, finishLabel, labelCount);
  }

  private int repeatInternal(
      @NotNull PseudocodeImpl originalPseudocode,
      @Nullable Label startLabel,
      @Nullable Label finishLabel,
      int labelCount) {
    Integer startIndex =
        startLabel != null
            ? ((PseudocodeLabel) startLabel).getTargetInstructionIndex()
            : Integer.valueOf(0);
    assert startIndex != null;
    Integer finishIndex =
        finishLabel != null
            ? ((PseudocodeLabel) finishLabel).getTargetInstructionIndex()
            : Integer.valueOf(originalPseudocode.mutableInstructionList.size());
    assert finishIndex != null;

    Map<Label, Label> originalToCopy = Maps.newLinkedHashMap();
    Multimap<Instruction, Label> originalLabelsForInstruction = HashMultimap.create();
    for (PseudocodeLabel label : originalPseudocode.labels) {
      Integer index = label.getTargetInstructionIndex();
      if (index == null) continue; // label is not bounded yet
      if (label == startLabel || label == finishLabel) continue;

      if (startIndex <= index && index <= finishIndex) {
        originalToCopy.put(label, label.copy(labelCount++));
        originalLabelsForInstruction.put(getJumpTarget(label), label);
      }
    }
    for (Label label : originalToCopy.values()) {
      labels.add((PseudocodeLabel) label);
    }
    for (int index = startIndex; index < finishIndex; index++) {
      Instruction originalInstruction = originalPseudocode.mutableInstructionList.get(index);
      repeatLabelsBindingForInstruction(
          originalInstruction, originalToCopy, originalLabelsForInstruction);
      Instruction copy = copyInstruction(originalInstruction, originalToCopy);
      addInstruction(copy);
      if (originalInstruction == originalPseudocode.errorInstruction
          && copy instanceof SubroutineExitInstruction) {
        errorInstruction = (SubroutineExitInstruction) copy;
      }
      if (originalInstruction == originalPseudocode.exitInstruction
          && copy instanceof SubroutineExitInstruction) {
        exitInstruction = (SubroutineExitInstruction) copy;
      }
      if (originalInstruction == originalPseudocode.sinkInstruction
          && copy instanceof SubroutineSinkInstruction) {
        sinkInstruction = (SubroutineSinkInstruction) copy;
      }
    }
    if (finishIndex < mutableInstructionList.size()) {
      repeatLabelsBindingForInstruction(
          originalPseudocode.mutableInstructionList.get(finishIndex),
          originalToCopy,
          originalLabelsForInstruction);
    }
    return labelCount;
  }

  private void repeatLabelsBindingForInstruction(
      @NotNull Instruction originalInstruction,
      @NotNull Map<Label, Label> originalToCopy,
      @NotNull Multimap<Instruction, Label> originalLabelsForInstruction) {
    for (Label originalLabel : originalLabelsForInstruction.get(originalInstruction)) {
      bindLabel(originalToCopy.get(originalLabel));
    }
  }

  private static Instruction copyInstruction(
      @NotNull Instruction instruction, @NotNull Map<Label, Label> originalToCopy) {
    if (instruction instanceof AbstractJumpInstruction) {
      Label originalTarget = ((AbstractJumpInstruction) instruction).getTargetLabel();
      if (originalToCopy.containsKey(originalTarget)) {
        return ((AbstractJumpInstruction) instruction).copy(originalToCopy.get(originalTarget));
      }
    }
    if (instruction instanceof NondeterministicJumpInstruction) {
      List<Label> originalTargets =
          ((NondeterministicJumpInstruction) instruction).getTargetLabels();
      List<Label> copyTargets = copyLabels(originalTargets, originalToCopy);
      return ((NondeterministicJumpInstruction) instruction).copy(copyTargets);
    }
    return ((InstructionImpl) instruction).copy();
  }

  @NotNull
  private static List<Label> copyLabels(
      Collection<Label> labels, Map<Label, Label> originalToCopy) {
    List<Label> newLabels = Lists.newArrayList();
    for (Label label : labels) {
      Label newLabel = originalToCopy.get(label);
      newLabels.add(newLabel != null ? newLabel : label);
    }
    return newLabels;
  }
}
Пример #12
0
/**
 * Represents one network of nodes, where each nodes is somehow connected to another within the
 * network.
 *
 * <p>Network contains following node types: - networking nodes - nodes that are a back-bone of a
 * network. These allow to connect multiple nodes in the network. A networking node "conducts" the
 * "signal" of the network to nodes defined in the "connectingOnSides" nodes in its vicinity. - leaf
 * nodes - nodes that are only receiving or producing a signal, and do not themselves "conduct" it
 * to other nodes.
 *
 * <p>A couple of non-obvious facts: 1. The same node (defined as location) cannot be both a
 * networking node and a leaf node in the same network. 2. The same leaf node can be a member of
 * multiple disjunctive networks (different network on each side). 3. A valid network can have no
 * networking nodes at all, and exactly two leaf nodes (neighbouring leaf nodes).
 *
 * @author Marcin Sciesinski <*****@*****.**>
 */
public class SimpleNetwork implements Network {
  private static final boolean SANITY_CHECK = false;
  private SetMultimap<ImmutableBlockLocation, NetworkNode> networkingNodes = HashMultimap.create();
  private SetMultimap<ImmutableBlockLocation, NetworkNode> leafNodes = HashMultimap.create();

  // Distance cache
  private Map<TwoNetworkNodes, Integer> distanceCache = Maps.newHashMap();

  public static SimpleNetwork createDegenerateNetwork(
      NetworkNode networkNode1, NetworkNode networkNode2) {
    if (!areNodesConnecting(networkNode1, networkNode2))
      throw new IllegalArgumentException("These two nodes are not connected");

    SimpleNetwork network = new SimpleNetwork();
    network.leafNodes.put(networkNode1.location, networkNode1);
    network.leafNodes.put(networkNode2.location, networkNode2);
    return network;
  }

  /**
   * Adds a networking node to the network.
   *
   * @param networkNode Definition of the networking node position and connecting sides.
   */
  public void addNetworkingNode(NetworkNode networkNode) {
    if (SANITY_CHECK && !canAddNetworkingNode(networkNode))
      throw new IllegalStateException("Unable to add this node to network");
    networkingNodes.put(networkNode.location, networkNode);
    distanceCache.clear();
  }

  /**
   * Adds a leaf node to the network.
   *
   * @param networkNode Definition of the leaf node position and connecting sides.
   */
  public void addLeafNode(NetworkNode networkNode) {
    if (SANITY_CHECK && (!canAddLeafNode(networkNode) || isEmptyNetwork()))
      throw new IllegalStateException("Unable to add this node to network");
    leafNodes.put(networkNode.location, networkNode);
    distanceCache.clear();
  }

  /**
   * Returns the network size - a number of nodes it spans. If the same node is added twice with
   * different connecting sides, it is counted twice.
   *
   * @return The sum of networking nodes and leaf nodes (count).
   */
  @Override
  public int getNetworkSize() {
    return networkingNodes.size() + leafNodes.size();
  }

  /**
   * Removes a leaf node from the network. If this removal made the network degenerate, it will
   * return <code>true</code>.
   *
   * @param networkingNode Definition of the leaf node position and connecting sides.
   * @return <code>true</code> if the network after the removal is degenerated or empty (no longer
   *     valid).
   */
  public boolean removeLeafNode(NetworkNode networkingNode) {
    // Removal of a leaf node cannot split the network, so it's just safe to remove it
    // We just need to check, if after removal of the node, network becomes degenerated, if so - we
    // need
    // to signal that the network is no longer valid and should be removed.
    final boolean changed = leafNodes.remove(networkingNode.location, networkingNode);
    if (!changed)
      throw new IllegalStateException("Tried to remove a node that is not in the network");

    distanceCache.clear();

    return isDegeneratedNetwork() || isEmptyNetwork();
  }

  public void removeAllLeafNodes() {
    leafNodes.clear();
    distanceCache.clear();
  }

  public void removeAllNetworkingNodes() {
    networkingNodes.clear();
    distanceCache.clear();
  }

  public void removeNetworkingNode(NetworkNode networkNode) {
    if (!networkingNodes.remove(networkNode.location, networkNode))
      throw new IllegalStateException("Tried to remove a node that is not in the network");
    distanceCache.clear();
  }

  public Collection<NetworkNode> getNetworkingNodes() {
    return Collections.unmodifiableCollection(networkingNodes.values());
  }

  public Collection<NetworkNode> getLeafNodes() {
    return Collections.unmodifiableCollection(leafNodes.values());
  }

  public static boolean areNodesConnecting(NetworkNode node1, NetworkNode node2) {
    for (Side side : SideBitFlag.getSides(node1.connectionSides)) {
      final ImmutableBlockLocation possibleConnectedLocation = node1.location.move(side);
      if (node2.location.equals(possibleConnectedLocation)
          && SideBitFlag.hasSide(node2.connectionSides, side.reverse())) return true;
    }
    return false;
  }

  /**
   * If this network can connect to node at the location specified with the specified connecting
   * sides.
   *
   * @param networkNode Definition of the networking node position and connecting sides.
   * @return If the networking node can be added to the network (connects to it).
   */
  public boolean canAddNetworkingNode(NetworkNode networkNode) {
    if (isEmptyNetwork()) return true;
    if (networkingNodes.containsValue(networkNode) || leafNodes.containsValue(networkNode))
      return false;
    return canConnectToNetworkingNode(networkNode);
  }

  public boolean canAddLeafNode(NetworkNode networkNode) {
    if (isEmptyNetwork()) return false;
    if (networkingNodes.containsValue(networkNode) || leafNodes.containsValue(networkNode))
      return false;

    return canConnectToNetworkingNode(networkNode);
  }

  private boolean canConnectToNetworkingNode(NetworkNode networkNode) {
    for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
      final ImmutableBlockLocation possibleConnectionLocation =
          networkNode.location.move(connectingOnSide);
      for (NetworkNode possibleConnectedNode : networkingNodes.get(possibleConnectionLocation)) {
        if (SideBitFlag.hasSide(possibleConnectedNode.connectionSides, connectingOnSide.reverse()))
          return true;
      }
    }
    return false;
  }

  @Override
  public boolean hasNetworkingNode(NetworkNode networkNode) {
    return networkingNodes.containsValue(networkNode);
  }

  @Override
  public boolean hasLeafNode(NetworkNode networkNode) {
    return leafNodes.containsValue(networkNode);
  }

  @Override
  public int getDistance(NetworkNode from, NetworkNode to) {
    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    final Integer cachedDistance = distanceCache.get(nodePair);
    if (cachedDistance != null) return cachedDistance;

    if ((!hasNetworkingNode(from) && !hasLeafNode(from))
        || (!hasNetworkingNode(to) && !hasLeafNode(to)))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    if (from.equals(to)) return 0;

    if (SimpleNetwork.areNodesConnecting(from, to)) return 1;

    // Breadth-first search of the network
    Set<NetworkNode> visitedNodes = Sets.newHashSet();
    visitedNodes.add(from);

    Set<NetworkNode> networkingNodesToTest = Sets.newHashSet();
    listConnectedNotVisitedNetworkingNodes(visitedNodes, from, networkingNodesToTest);
    int distanceSearched = 1;
    while (networkingNodesToTest.size() > 0) {
      distanceSearched++;

      for (NetworkNode nodeToTest : networkingNodesToTest) {
        if (SimpleNetwork.areNodesConnecting(nodeToTest, to)) {
          distanceCache.put(new TwoNetworkNodes(from, to), distanceSearched);
          return distanceSearched;
        }
        visitedNodes.add(nodeToTest);
      }

      Set<NetworkNode> nextNetworkingNodesToTest = Sets.newHashSet();
      for (NetworkNode nodeToTest : networkingNodesToTest)
        listConnectedNotVisitedNetworkingNodes(visitedNodes, nodeToTest, nextNetworkingNodesToTest);

      networkingNodesToTest = nextNetworkingNodesToTest;
    }
    return -1;
  }

  @Override
  public boolean isInDistance(int distance, NetworkNode from, NetworkNode to) {
    if (distance < 0) throw new IllegalArgumentException("distance must be >= 0");

    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    final Integer cachedDistance = distanceCache.get(nodePair);
    if (cachedDistance != null) return cachedDistance <= distance;

    if ((!hasNetworkingNode(from) && !hasLeafNode(from))
        || (!hasNetworkingNode(to) && !hasLeafNode(to)))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    return isInDistanceInternal(distance, from, to, nodePair);
  }

  private boolean isInDistanceInternal(
      int distance, NetworkNode from, NetworkNode to, TwoNetworkNodes cachePairKey) {
    if (from.equals(to)) return true;

    if (distance == 0) return false;

    if (SimpleNetwork.areNodesConnecting(from, to)) return true;

    // Breadth-first search of the network
    Set<NetworkNode> visitedNodes = Sets.newHashSet();
    visitedNodes.add(from);

    Set<NetworkNode> networkingNodesToTest = Sets.newHashSet();
    listConnectedNotVisitedNetworkingNodes(visitedNodes, from, networkingNodesToTest);
    int distanceSearched = 1;
    while (distanceSearched < distance) {
      distanceSearched++;

      for (NetworkNode nodeToTest : networkingNodesToTest) {
        if (SimpleNetwork.areNodesConnecting(nodeToTest, to)) {
          distanceCache.put(cachePairKey, distanceSearched);
          return true;
        }
        visitedNodes.add(nodeToTest);
      }

      Set<NetworkNode> nextNetworkingNodesToTest = Sets.newHashSet();
      for (NetworkNode nodeToTest : networkingNodesToTest)
        listConnectedNotVisitedNetworkingNodes(visitedNodes, nodeToTest, nextNetworkingNodesToTest);

      networkingNodesToTest = nextNetworkingNodesToTest;
    }

    return false;
  }

  @Override
  public boolean isInDistanceWithSide(int distance, NetworkNode from, NetworkNode to, Side toSide) {
    to = new NetworkNode(to.location.toVector3i(), toSide);
    TwoNetworkNodes nodePair = new TwoNetworkNodes(from, to);
    return isInDistanceInternal(distance, from, to, nodePair);
  }

  @Override
  public byte getLeafSidesInNetwork(NetworkNode networkNode) {
    if (!hasLeafNode(networkNode))
      throw new IllegalArgumentException("Cannot test nodes not in network");

    if (networkingNodes.size() == 0) {
      // Degenerated network
      for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
        Vector3i possibleLocation = networkNode.location.toVector3i();
        possibleLocation.add(connectingOnSide.getVector3i());
        for (NetworkNode node : leafNodes.get(new ImmutableBlockLocation(possibleLocation))) {
          if (SideBitFlag.hasSide(node.connectionSides, connectingOnSide.reverse())) {
            return SideBitFlag.getSide(connectingOnSide);
          }
        }
      }

      return 0;
    } else {
      byte result = 0;
      for (Side connectingOnSide : SideBitFlag.getSides(networkNode.connectionSides)) {
        Vector3i possibleLocation = networkNode.location.toVector3i();
        possibleLocation.add(connectingOnSide.getVector3i());
        for (NetworkNode node : networkingNodes.get(new ImmutableBlockLocation(possibleLocation))) {
          if (SideBitFlag.hasSide(node.connectionSides, connectingOnSide.reverse())) {
            result += SideBitFlag.getSide(connectingOnSide);
          }
        }
      }

      return result;
    }
  }

  //    public Map<Vector3i, Byte> getConnectedNodes(Vector3i location, byte connectionSides) {
  //        Map<Vector3i, Byte> result = Maps.newHashMap();
  //        for (Direction connectingOnSide : DirectionsUtil.getDirections(connectionSides)) {
  //            final Vector3i possibleNodeLocation = new Vector3i(location);
  //            possibleNodeLocation.add(connectingOnSide.getVector3i());
  //
  //            final Byte directionsForNodeOnThatSide = networkingNodes.get(possibleNodeLocation);
  //            if (directionsForNodeOnThatSide != null &&
  // DirectionsUtil.hasDirection(directionsForNodeOnThatSide, connectingOnSide.reverse()))
  //                result.put(possibleNodeLocation, directionsForNodeOnThatSide);
  //
  //            for (byte directionsForLeafNodeOnThatSide : leafNodes.get(possibleNodeLocation)) {
  //                if (DirectionsUtil.hasDirection(directionsForLeafNodeOnThatSide,
  // connectingOnSide.reverse()))
  //                    result.put(possibleNodeLocation, directionsForLeafNodeOnThatSide);
  //            }
  //        }
  //        return result;
  //    }

  private void listConnectedNotVisitedNetworkingNodes(
      Set<NetworkNode> visitedNodes, NetworkNode location, Collection<NetworkNode> result) {
    for (Side connectingOnSide : SideBitFlag.getSides(location.connectionSides)) {
      final ImmutableBlockLocation possibleConnectionLocation =
          location.location.move(connectingOnSide);
      for (NetworkNode possibleConnection : networkingNodes.get(possibleConnectionLocation)) {
        if (!visitedNodes.contains(possibleConnection)
            && SideBitFlag.hasSide(possibleConnection.connectionSides, connectingOnSide.reverse()))
          result.add(possibleConnection);
      }
    }
  }

  private boolean isDegeneratedNetwork() {
    return networkingNodes.isEmpty() && leafNodes.size() == 1;
  }

  private boolean isEmptyNetwork() {
    return networkingNodes.isEmpty() && leafNodes.isEmpty();
  }
}
Пример #13
0
 /** For JSON and XML serialization only. */
 @JsonProperty("attributes")
 @SuppressWarnings("unused")
 private Map<String, Object> getOtherAttributes() {
   final Map<String, Object> otherAttributes = Maps.newHashMap(attributesView);
   return otherAttributes.isEmpty() ? null : otherAttributes;
 }
Пример #14
0
  /**
   * Parse the given array of arguments.
   *
   * <p>Empty arguments are removed from the list of arguments.
   *
   * @param args an array with arguments
   * @param expectedValueFlags a set containing all value flags (pass null to disable value flag
   *     parsing)
   * @param allowHangingFlag true if hanging flags are allowed
   * @param namespace the locals, null to create empty one
   * @throws CommandException thrown on a parsing error
   */
  public CommandContext(
      String[] args,
      Set<Character> expectedValueFlags,
      boolean allowHangingFlag,
      Namespace namespace)
      throws CommandException {
    if (expectedValueFlags == null) {
      expectedValueFlags = Collections.emptySet();
    }

    originalArgs = args;
    command = args[0];
    this.namespace = namespace != null ? namespace : new Namespace();
    boolean isHanging = false;
    SuggestionContext suggestionContext = SuggestionContext.hangingValue();

    // Eliminate empty args and combine multiword args first
    List<Integer> argIndexList = new ArrayList<Integer>(args.length);
    List<String> argList = new ArrayList<String>(args.length);
    for (int i = 1; i < args.length; ++i) {
      isHanging = false;

      String arg = args[i];
      if (arg.isEmpty()) {
        isHanging = true;
        continue;
      }

      argIndexList.add(i);

      switch (arg.charAt(0)) {
        case '\'':
        case '"':
          final StringBuilder build = new StringBuilder();
          final char quotedChar = arg.charAt(0);

          int endIndex;
          for (endIndex = i; endIndex < args.length; ++endIndex) {
            final String arg2 = args[endIndex];
            if (arg2.charAt(arg2.length() - 1) == quotedChar && arg2.length() > 1) {
              if (endIndex != i) build.append(' ');
              build.append(arg2.substring(endIndex == i ? 1 : 0, arg2.length() - 1));
              break;
            } else if (endIndex == i) {
              build.append(arg2.substring(1));
            } else {
              build.append(' ').append(arg2);
            }
          }

          if (endIndex < args.length) {
            arg = build.toString();
            i = endIndex;
          }

          // In case there is an empty quoted string
          if (arg.isEmpty()) {
            continue;
          }
          // else raise exception about hanging quotes?
      }
      argList.add(arg);
    }

    // Then flags

    List<Integer> originalArgIndices = Lists.newArrayListWithCapacity(argIndexList.size());
    List<String> parsedArgs = Lists.newArrayListWithCapacity(argList.size());
    Map<Character, String> valueFlags = Maps.newHashMap();
    List<Character> booleanFlags = Lists.newArrayList();

    for (int nextArg = 0; nextArg < argList.size(); ) {
      // Fetch argument
      String arg = argList.get(nextArg++);
      suggestionContext = SuggestionContext.hangingValue();

      // Not a flag?
      if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z\\?]+$")) {
        if (!isHanging) {
          suggestionContext = SuggestionContext.lastValue();
        }

        originalArgIndices.add(argIndexList.get(nextArg - 1));
        parsedArgs.add(arg);
        continue;
      }

      // Handle flag parsing terminator --
      if (arg.equals("--")) {
        while (nextArg < argList.size()) {
          originalArgIndices.add(argIndexList.get(nextArg));
          parsedArgs.add(argList.get(nextArg++));
        }
        break;
      }

      // Go through the flag characters
      for (int i = 1; i < arg.length(); ++i) {
        char flagName = arg.charAt(i);

        if (expectedValueFlags.contains(flagName)) {
          if (valueFlags.containsKey(flagName)) {
            throw new CommandException("Value flag '" + flagName + "' already given");
          }

          if (nextArg >= argList.size()) {
            if (allowHangingFlag) {
              suggestionContext = SuggestionContext.flag(flagName);
              break;
            } else {
              throw new CommandException("No value specified for the '-" + flagName + "' flag.");
            }
          }

          // If it is a value flag, read another argument and add it
          valueFlags.put(flagName, argList.get(nextArg++));
          if (!isHanging) {
            suggestionContext = SuggestionContext.flag(flagName);
          }
        } else {
          booleanFlags.add(flagName);
        }
      }
    }

    ImmutableMap.Builder<Character, String> allFlagsBuilder =
        new ImmutableMap.Builder<Character, String>().putAll(valueFlags);
    for (Character flag : booleanFlags) {
      allFlagsBuilder.put(flag, "true");
    }

    this.parsedArgs = ImmutableList.copyOf(parsedArgs);
    this.originalArgIndices = ImmutableList.copyOf(originalArgIndices);
    this.booleanFlags = ImmutableSet.copyOf(booleanFlags);
    this.valueFlags = ImmutableMap.copyOf(valueFlags);
    this.allFlags = allFlagsBuilder.build();
    this.suggestionContext = suggestionContext;
  }
Пример #15
0
/**
 * Encapsulates the results of processing. Provides access to the values of attributes collected
 * after processing and utility methods for obtaining processed documents ( {@link
 * #getDocuments()})) and the created clusters ({@link #getClusters()}).
 */
@Root(name = "searchresult", strict = false)
public final class ProcessingResult {
  /** Attributes collected after processing */
  private Map<String, Object> attributes = Maps.newHashMap();

  /** Read-only view of attributes exposed in {@link #getAttributes()} */
  private Map<String, Object> attributesView;

  /**
   * Query field used during serialization/ deserialization, see {@link #afterDeserialization()} and
   * {@link #beforeSerialization()}
   */
  @Element(required = false)
  private String query;

  /**
   * Documents field used during serialization/ deserialization, see {@link #afterDeserialization()}
   * and {@link #beforeSerialization()}
   */
  @ElementList(inline = true, required = false)
  private List<Document> documents;

  /**
   * Clusters field used during serialization/ deserialization, see {@link #afterDeserialization()}
   * and {@link #beforeSerialization()}
   */
  @ElementList(inline = true, required = false)
  private List<Cluster> clusters;

  /** Attributes of this result for serialization/ deserialization purposes. */
  @ElementMap(entry = "attribute", key = "key", attribute = true, inline = true, required = false)
  private HashMap<String, SimpleXmlWrapperValue> otherAttributesForSerialization;

  /** Parameterless constructor required for XML serialization/ deserialization. */
  ProcessingResult() {
    this(new HashMap<String, Object>());
  }

  /**
   * Creates a {@link ProcessingResult} with the provided <code>attributes</code>. Assigns unique
   * document identifiers if documents are present in the <code>attributes</code> map (under the key
   * {@link AttributeNames#DOCUMENTS}).
   */
  @SuppressWarnings("unchecked")
  ProcessingResult(Map<String, Object> attributes) {
    this.attributes = attributes;

    // Replace a modifiable collection of documents with an unmodifiable one
    final List<Document> documents = (List<Document>) attributes.get(AttributeNames.DOCUMENTS);
    if (documents != null) {
      Document.assignDocumentIds(documents);
      attributes.put(AttributeNames.DOCUMENTS, Collections.unmodifiableList(documents));
    }

    // Replace a modifiable collection of clusters with an unmodifiable one
    final List<Cluster> clusters = (List<Cluster>) attributes.get(AttributeNames.CLUSTERS);
    if (clusters != null) {
      Cluster.assignClusterIds(clusters);
      attributes.put(AttributeNames.CLUSTERS, Collections.unmodifiableList(clusters));
    }

    // Store a reference to attributes as an unmodifiable map
    this.attributesView = Collections.unmodifiableMap(attributes);
  }

  /**
   * Returns attributes fed-in and collected during processing. The returned map is unmodifiable.
   *
   * @return attributes fed-in and collected during processing
   */
  public Map<String, Object> getAttributes() {
    return attributesView;
  }

  /**
   * Returns a specific attribute of this result set. This method is equivalent to calling {@link
   * #getAttributes()} and then getting the required attribute from the map.
   *
   * @param key key of the attribute to return
   * @return value of the attribute
   */
  @SuppressWarnings("unchecked")
  public <T> T getAttribute(String key) {
    return (T) attributesView.get(key);
  }

  /**
   * Returns the documents that have been processed. The returned collection is unmodifiable.
   *
   * @return documents that have been processed or <code>null</code> if no documents are present in
   *     the result.
   */
  @SuppressWarnings("unchecked")
  public List<Document> getDocuments() {
    return (List<Document>) attributes.get(AttributeNames.DOCUMENTS);
  }

  /*
   * TODO: Returning a list of clusters instead of a (possibly artificial) cluster with
   * subclusters adds a little complexity to recursive methods operating on clusters (a
   * natural entry point is a method taking one cluster and acting on subclusters
   * recursively). If we have to start with a list of clusters, we have to handle this
   * special case separately...
   */

  /**
   * Returns the clusters that have been created during processing. The returned list is
   * unmodifiable.
   *
   * @return clusters created during processing or <code>null</code> if no clusters were present in
   *     the result.
   */
  @SuppressWarnings("unchecked")
  public List<Cluster> getClusters() {
    return (List<Cluster>) attributes.get(AttributeNames.CLUSTERS);
  }

  /** Extracts document and cluster lists before serialization. */
  @Persist
  private void beforeSerialization() {
    /*
     * See http://issues.carrot2.org/browse/CARROT-693; this monitor does not save us
     * in multi-threaded environment anyway. A better solution would be to prepare
     * this eagerly in the constructor, but we try to balance overhead and full
     * correctness here.
     */
    synchronized (this) {
      query = (String) attributes.get(AttributeNames.QUERY);

      if (getDocuments() != null) {
        documents = Lists.newArrayList(getDocuments());
      } else {
        documents = null;
      }

      if (getClusters() != null) {
        clusters = Lists.newArrayList(getClusters());
      } else {
        clusters = null;
      }

      otherAttributesForSerialization = MapUtils.asHashMap(SimpleXmlWrappers.wrap(attributes));
      otherAttributesForSerialization.remove(AttributeNames.QUERY);
      otherAttributesForSerialization.remove(AttributeNames.CLUSTERS);
      otherAttributesForSerialization.remove(AttributeNames.DOCUMENTS);
      if (otherAttributesForSerialization.isEmpty()) {
        otherAttributesForSerialization = null;
      }
    }
  }

  /** Transfers document and cluster lists to the attributes map after deserialization. */
  @Commit
  private void afterDeserialization() throws Exception {
    if (otherAttributesForSerialization != null) {
      attributes = SimpleXmlWrappers.unwrap(otherAttributesForSerialization);
    }

    attributesView = Collections.unmodifiableMap(attributes);

    attributes.put(AttributeNames.QUERY, query != null ? query.trim() : null);
    attributes.put(AttributeNames.DOCUMENTS, documents);
    attributes.put(AttributeNames.CLUSTERS, clusters);

    // Convert document ids to the actual references
    if (clusters != null && documents != null) {
      final Map<String, Document> documentsById = Maps.newHashMap();
      for (Document document : documents) {
        documentsById.put(document.getStringId(), document);
      }

      for (Cluster cluster : clusters) {
        documentIdToReference(cluster, documentsById);
      }
    }
  }

  /** Replace document refids with the actual references upon deserialization. */
  private void documentIdToReference(Cluster cluster, Map<String, Document> documents) {
    if (cluster.documentIds != null) {
      for (Cluster.DocumentRefid documentRefid : cluster.documentIds) {
        cluster.addDocuments(documents.get(documentRefid.refid));
      }
    }

    for (Cluster subcluster : cluster.getSubclusters()) {
      documentIdToReference(subcluster, documents);
    }
  }

  /** Serializes this {@link ProcessingResult} to an XML string. */
  public String serialize() {
    try {
      StringWriter sw = new StringWriter();
      new Persister().write(this, sw);
      return sw.toString();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Serializes this {@link ProcessingResult} to an XML stream. The output includes all documents,
   * clusters and other attributes.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param stream the stream to serialize this {@link ProcessingResult} to. The stream will
   *     <strong>not</strong> be closed.
   * @throws Exception in case of any problems with serialization
   */
  public void serialize(OutputStream stream) throws Exception {
    serialize(stream, true, true);
  }

  /**
   * Serializes this {@link ProcessingResult} to a byte stream. Documents and clusters can be
   * included or skipped in the output as requested. Other attributes are always included.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param stream the stream to serialize this {@link ProcessingResult} to. The stream will
   *     <strong>not</strong> be closed.
   * @param saveDocuments if <code>false</code>, documents will not be serialized. Notice that when
   *     deserializing XML containing clusters but not documents, document references in {@link
   *     Cluster#getDocuments()} will not be restored.
   * @param saveClusters if <code>false</code>, clusters will not be serialized
   * @throws Exception in case of any problems with serialization
   */
  public void serialize(OutputStream stream, boolean saveDocuments, boolean saveClusters)
      throws Exception {
    serialize(stream, saveDocuments, saveClusters, true);
  }

  /**
   * Serializes this {@link ProcessingResult} to a byte stream. Documents, clusters and other
   * attributes can be included or skipped in the output as requested.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param stream the stream to serialize this {@link ProcessingResult} to. The stream will
   *     <strong>not</strong> be closed.
   * @param saveDocuments if <code>false</code>, documents will not be serialized. Notice that when
   *     deserializing XML containing clusters but not documents, document references in {@link
   *     Cluster#getDocuments()} will not be restored.
   * @param saveClusters if <code>false</code>, clusters will not be serialized
   * @param saveOtherAttributes if <code>false</code>, other attributes will not be serialized
   * @throws Exception in case of any problems with serialization
   */
  public void serialize(
      OutputStream stream, boolean saveDocuments, boolean saveClusters, boolean saveOtherAttributes)
      throws Exception {
    final Map<String, Object> backupAttributes = attributes;

    attributes =
        prepareAttributesForSerialization(saveDocuments, saveClusters, saveOtherAttributes);

    new Persister().write(this, stream);

    attributes = backupAttributes;
  }

  /** Deserialize from an input stream of characters. */
  public static ProcessingResult deserialize(CharSequence input) throws Exception {
    return new Persister().read(ProcessingResult.class, input.toString());
  }

  /**
   * Deserializes a {@link ProcessingResult} from an XML stream.
   *
   * @param input the input XML stream to deserialize a {@link ProcessingResult} from. The stream
   *     will <strong>not</strong> be closed.
   * @return deserialized {@link ProcessingResult}
   * @throws Exception is case of any problems with deserialization
   */
  public static ProcessingResult deserialize(InputStream input) throws Exception {
    return new Persister().read(ProcessingResult.class, input);
  }

  /**
   * Serializes this processing result as JSON to the provided <code>writer</code>. The output
   * includes all documents, clusters and other attributes.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param writer the writer to serialize this processing result to. The writer will
   *     <strong>not</strong> be closed.
   * @throws IOException in case of any problems with serialization
   */
  public void serializeJson(Writer writer) throws IOException {
    serializeJson(writer, null);
  }

  /**
   * Serializes this processing result as JSON to the provided <code>writer</code>. The output
   * includes all documents, clusters and other attributes.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param writer the writer to serialize this processing result to. The writer will
   *     <strong>not</strong> be closed.
   * @param callback JavaScript function name in which to wrap the JSON response or <code>null
   *     </code>.
   * @throws IOException in case of any problems with serialization
   */
  public void serializeJson(Writer writer, String callback) throws IOException {
    serializeJson(writer, callback, true, true);
  }

  /**
   * Serializes this processing result as JSON to the provided <code>writer</code>. Documents and
   * clusters can be included or skipped in the output as requested. Other attributes are always
   * included.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param writer the writer to serialize this processing result to. The writer will
   *     <strong>not</strong> be closed.
   * @param callback JavaScript function name in which to wrap the JSON response or <code>null
   *     </code>.
   * @param saveDocuments if <code>false</code>, documents will not be serialized.
   * @param saveClusters if <code>false</code>, clusters will not be serialized
   * @throws IOException in case of any problems with serialization
   */
  public void serializeJson(
      Writer writer, String callback, boolean saveDocuments, boolean saveClusters)
      throws IOException {
    serializeJson(writer, callback, false, saveDocuments, saveClusters);
  }

  /**
   * Serializes this processing result as JSON to the provided <code>writer</code>.
   *
   * <p>This method is not thread-safe, external synchronization must be applied if needed.
   *
   * @param writer the writer to serialize this processing result to. The writer will
   *     <strong>not</strong> be closed.
   * @param callback JavaScript function name in which to wrap the JSON response or <code>null
   *     </code>.
   * @param indent if <code>true</code>, the output JSON will be pretty-printed
   * @param saveDocuments if <code>false</code>, documents will not be serialized.
   * @param saveClusters if <code>false</code>, clusters will not be serialized
   * @throws IOException in case of any problems with serialization
   */
  public void serializeJson(
      Writer writer, String callback, boolean indent, boolean saveDocuments, boolean saveClusters)
      throws IOException {
    serializeJson(writer, callback, indent, saveDocuments, saveClusters, true);
  }

  /**
   * Serializes this processing result as JSON to the provided <code>writer</code>. Documents,
   * clusters and other attributes can be included or skipped in the output as requested.
   *
   * @param writer the writer to serialize this processing result to. The writer will
   *     <strong>not</strong> be closed.
   * @param callback JavaScript function name in which to wrap the JSON response or <code>null
   *     </code>.
   * @param indent if <code>true</code>, the output JSON will be pretty-printed
   * @param saveDocuments if <code>false</code>, documents will not be serialized.
   * @param saveClusters if <code>false</code>, clusters will not be serialized
   * @param saveOtherAttributes if <code>false</code>, other attributes will not be serialized
   * @throws IOException in case of any problems with serialization
   */
  public void serializeJson(
      Writer writer,
      String callback,
      boolean indent,
      boolean saveDocuments,
      boolean saveClusters,
      boolean saveOtherAttributes)
      throws IOException {
    final ObjectMapper mapper = new ObjectMapper();
    mapper.getFactory().disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);
    mapper.enable(SerializationFeature.INDENT_OUTPUT);

    if (StringUtils.isNotBlank(callback)) {
      writer.write(callback + "(");
    }
    final Map<String, Object> attrs =
        prepareAttributesForSerialization(saveDocuments, saveClusters, saveOtherAttributes);

    mapper.writeValue(writer, attrs);
    if (StringUtils.isNotBlank(callback)) {
      writer.write(");");
    }
  }

  /**
   * Prepares a temporary attributes map for serialization purposes. Includes only the requested
   * elements in the map.
   */
  private Map<String, Object> prepareAttributesForSerialization(
      boolean saveDocuments, boolean saveClusters, boolean saveOtherAttributes) {
    final Map<String, Object> tempAttributes = Maps.newHashMap();

    if (saveOtherAttributes) {
      tempAttributes.putAll(attributes);
      tempAttributes.remove(AttributeNames.DOCUMENTS);
      tempAttributes.remove(AttributeNames.CLUSTERS);
    } else {
      tempAttributes.put(AttributeNames.QUERY, attributes.get(AttributeNames.QUERY));
    }

    if (saveDocuments) {
      tempAttributes.put(AttributeNames.DOCUMENTS, getDocuments());
    }

    if (saveClusters) {
      tempAttributes.put(AttributeNames.CLUSTERS, getClusters());
    }

    return tempAttributes;
  }
}