/*
 * Copyright 2000-2009 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 com.intellij.debugger.ui.tree.render;

import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerContext;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerUtils;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.ui.tree.DebuggerTreeNode;
import com.intellij.debugger.ui.tree.NodeDescriptor;
import com.intellij.debugger.ui.tree.ValueDescriptor;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.JDOMExternalizerUtil;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.PsiExpression;
import com.intellij.ui.classFilter.ClassFilter;
import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
import com.sun.jdi.*;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;

import java.util.Iterator;

public class ToStringRenderer extends NodeRendererImpl {
  public static final @NonNls String UNIQUE_ID = "ToStringRenderer";

  private boolean USE_CLASS_FILTERS = false;
  private ClassFilter[] myClassFilters = ClassFilter.EMPTY_ARRAY;

  public ToStringRenderer() {
    setEnabled(true);
  }

  public String getUniqueId() {
    return UNIQUE_ID;
  }

  public @NonNls String getName() {
    return "toString";
  }

  public void setName(String name) {
    // prohibit change
  }

  public ToStringRenderer clone() {
    final ToStringRenderer cloned = (ToStringRenderer)super.clone();
    final ClassFilter[] classFilters = (myClassFilters.length > 0)? new ClassFilter[myClassFilters.length] : ClassFilter.EMPTY_ARRAY;
    for (int idx = 0; idx < classFilters.length; idx++) {
      classFilters[idx] = myClassFilters[idx].clone();
    }
    cloned.myClassFilters = classFilters;
    return cloned;
  }

  public String calcLabel(final ValueDescriptor valueDescriptor, EvaluationContext evaluationContext, final DescriptorLabelListener labelListener)
    throws EvaluateException {
    final Value value = valueDescriptor.getValue();
    BatchEvaluator.getBatchEvaluator(evaluationContext.getDebugProcess()).invoke(new ToStringCommand(evaluationContext, value) {
      public void evaluationResult(String message) {
        final String truncated = DebuggerUtilsEx.truncateString(message);
        valueDescriptor.setValueLabel(message != null ? "\"" + DebuggerUtils.convertToPresentationString(truncated) + "\"" : "");
        labelListener.labelChanged();
      }

      public void evaluationError(String message) {
        final String msg = value != null? message + " " + DebuggerBundle.message("evaluation.error.cannot.evaluate.tostring", value.type().name()) : message;
        valueDescriptor.setValueLabelFailed(new EvaluateException(msg, null));
        labelListener.labelChanged();
      }
    });
    return XDebuggerUIConstants.COLLECTING_DATA_MESSAGE;
  }

  public boolean isUseClassFilters() {
    return USE_CLASS_FILTERS;
  }

  public void setUseClassFilters(boolean value) {
    USE_CLASS_FILTERS = value;
  }

  public boolean isApplicable(Type type) {
    if(!(type instanceof ReferenceType)) {
      return false;
    }

    if(type.name().equals("java.lang.String")) {
      return false; // do not render 'String' objects for performance reasons
    }

    if(!overridesToString(type)) {
      return false;
    }

    if (USE_CLASS_FILTERS) {
      if (!isFiltered(type)) {
        return false;
      }
    }

    return true;
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  private static boolean overridesToString(Type type) {
    if(type instanceof ClassType) {
      final ClassType classType = (ClassType)type;
      final java.util.List methods = classType.methodsByName("toString", "()Ljava/lang/String;");
      if (methods.size() > 0) {
        for (Iterator iterator = methods.iterator(); iterator.hasNext();) {
          final Method method = (Method)iterator.next();
          if(!(method.declaringType().name()).equals("java.lang.Object")){
            return true;
          }
        }
      }
    }
    return false;
  }

  public void buildChildren(Value value, ChildrenBuilder builder, EvaluationContext evaluationContext) {
    final DebugProcessImpl debugProcess = (DebugProcessImpl)evaluationContext.getDebugProcess();
    debugProcess.getDefaultRenderer(value).buildChildren(value, builder, evaluationContext);
  }

  public PsiExpression getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) throws EvaluateException {
    final Value parentValue = ((ValueDescriptor)node.getParent().getDescriptor()).getValue();
    final DebugProcessImpl debugProcess = (DebugProcessImpl)context.getDebugProcess();
    return debugProcess.getDefaultRenderer(parentValue).getChildValueExpression(node, context);
  }

  public boolean isExpandable(Value value, EvaluationContext evaluationContext, NodeDescriptor parentDescriptor) {
    final DebugProcessImpl debugProcess = (DebugProcessImpl)evaluationContext.getDebugProcess();
    return debugProcess.getDefaultRenderer(value).isExpandable(value, evaluationContext, parentDescriptor);
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  public void readExternal(Element element) throws InvalidDataException {
    super.readExternal(element);
    final String value = JDOMExternalizerUtil.readField(element, "USE_CLASS_FILTERS");
    USE_CLASS_FILTERS = "true".equalsIgnoreCase(value);
    myClassFilters = DebuggerUtilsEx.readFilters(element.getChildren("filter"));
  }

  @SuppressWarnings({"HardCodedStringLiteral"})
  public void writeExternal(Element element) throws WriteExternalException {
    super.writeExternal(element);
    JDOMExternalizerUtil.writeField(element, "USE_CLASS_FILTERS", USE_CLASS_FILTERS? "true" : "false");
    DebuggerUtilsEx.writeFilters(element, "filter", myClassFilters);
  }

  public ClassFilter[] getClassFilters() {
    return myClassFilters;
  }

  public void setClassFilters(ClassFilter[] classFilters) {
    myClassFilters = classFilters != null? classFilters : ClassFilter.EMPTY_ARRAY;
  }

  private boolean isFiltered(Type t) {
    if (t instanceof ReferenceType) {
      for (ClassFilter classFilter : myClassFilters) {
        if (classFilter.isEnabled() && DebuggerUtilsEx.getSuperType(t, classFilter.getPattern()) != null) {
          return true;
        }
      }
    }
    return DebuggerUtilsEx.isFiltered(t.name(), myClassFilters);
  }
}

@@ -2937,82 +2937,8 @@
 ) %7B%0A
-        final String truncated = DebuggerUtilsEx.truncateString(message);%0A
     
@@ -2971,24 +2971,35 @@
 ueLabel(
+%0A          
 message 
 != null 
@@ -2994,17 +2994,21 @@
 age 
-!
+=
 = null
- 
 ?
+ %22%22 :
  %22%5C%22
@@ -3057,30 +3057,64 @@
 ing(
-truncated) + %22%5C%22%22 : %22%22
+DebuggerUtilsEx.truncateString(message)) + %22%5C%22%22%0A        
 );%0A 

fix NPE