private static String createShaderPrologue(List<String> presetDefines) {
   DefineList dl = new DefineList(presetDefines.size());
   for (int i = 0; i < presetDefines.size(); i++) {
     dl.set(i, 1);
   }
   StringBuilder sb = new StringBuilder();
   dl.generateSource(sb, presetDefines, null);
   return sb.toString();
 }
  private List<String> tokenizeTextureValue(final String value) {
    final List<String> matchList = new ArrayList<String>();
    final Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'");
    final Matcher regexMatcher = regex.matcher(value.trim());

    while (regexMatcher.find()) {
      if (regexMatcher.group(1) != null) {
        matchList.add(regexMatcher.group(1));
      } else if (regexMatcher.group(2) != null) {
        matchList.add(regexMatcher.group(2));
      } else {
        matchList.add(regexMatcher.group());
      }
    }

    return matchList;
  }
  private boolean isTexturePathDeclaredTheTraditionalWay(
      final List<TextureOptionValue> optionValues, final String texturePath) {
    final boolean startsWithOldStyle =
        texturePath.startsWith("Flip Repeat ")
            || texturePath.startsWith("Flip ")
            || texturePath.startsWith("Repeat ")
            || texturePath.startsWith("Repeat Flip ");

    if (!startsWithOldStyle) {
      return false;
    }

    if (optionValues.size() == 1
        && (optionValues.get(0).textureOption == TextureOption.Flip
            || optionValues.get(0).textureOption == TextureOption.Repeat)) {
      return true;
    } else if (optionValues.size() == 2
        && optionValues.get(0).textureOption == TextureOption.Flip
        && optionValues.get(1).textureOption == TextureOption.Repeat) {
      return true;
    } else if (optionValues.size() == 2
        && optionValues.get(0).textureOption == TextureOption.Repeat
        && optionValues.get(1).textureOption == TextureOption.Flip) {
      return true;
    }

    return false;
  }
  private List<TextureOptionValue> parseTextureOptions(final List<String> values) {
    final List<TextureOptionValue> matchList = new ArrayList<TextureOptionValue>();

    if (values.isEmpty() || values.size() == 1) {
      return matchList;
    }

    // Loop through all but the last value, the last one is going to be the path.
    for (int i = 0; i < values.size() - 1; i++) {
      final String value = values.get(i);
      final TextureOption textureOption = TextureOption.getTextureOption(value);

      if (textureOption == null
          && !value.contains("\\")
          && !value.contains("/")
          && !values.get(0).equals("Flip")
          && !values.get(0).equals("Repeat")) {
        logger.log(
            Level.WARNING,
            "Unknown texture option \"{0}\" encountered for \"{1}\" in material \"{2}\"",
            new Object[] {value, key, material.getKey().getName()});
      } else if (textureOption != null) {
        final String option = textureOption.getOptionValue(value);
        matchList.add(new TextureOptionValue(textureOption, option));
      }
    }

    return matchList;
  }
  private void loadFromRoot(List<Statement> roots) throws IOException {
    if (roots.size() == 2) {
      Statement exception = roots.get(0);
      String line = exception.getLine();
      if (line.startsWith("Exception")) {
        throw new AssetLoadException(line.substring("Exception ".length()));
      } else {
        throw new IOException("In multiroot material, expected first statement to be 'Exception'");
      }
    } else if (roots.size() != 1) {
      throw new IOException("Too many roots in J3M/J3MD file");
    }

    boolean extending = false;
    Statement materialStat = roots.get(0);
    String materialName = materialStat.getLine();
    if (materialName.startsWith("MaterialDef")) {
      materialName = materialName.substring("MaterialDef ".length()).trim();
      extending = false;
    } else if (materialName.startsWith("Material")) {
      materialName = materialName.substring("Material ".length()).trim();
      extending = true;
    } else {
      throw new IOException("Specified file is not a Material file");
    }

    String[] split = materialName.split(":", 2);

    if (materialName.equals("")) {
      throw new MatParseException("Material name cannot be empty", materialStat);
    }

    if (split.length == 2) {
      if (!extending) {
        throw new MatParseException("Must use 'Material' when extending.", materialStat);
      }

      String extendedMat = split[1].trim();

      MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat));
      if (def == null) {
        throw new MatParseException(
            "Extended material " + extendedMat + " cannot be found.", materialStat);
      }

      material = new Material(def);
      material.setKey(key);
      material.setName(split[0].trim());
      //            material.setAssetName(fileName);
    } else if (split.length == 1) {
      if (extending) {
        throw new MatParseException("Expected ':', got '{'", materialStat);
      }
      materialDef = new MaterialDef(assetManager, materialName);
      // NOTE: pass file name for defs so they can be loaded later
      materialDef.setAssetName(key.getName());
    } else {
      throw new MatParseException("Cannot use colon in material name/path", materialStat);
    }

    for (Statement statement : materialStat.getContents()) {
      split = statement.getLine().split("[ \\{]");
      String statType = split[0];
      if (extending) {
        if (statType.equals("MaterialParameters")) {
          readExtendingMaterialParams(statement.getContents());
        } else if (statType.equals("AdditionalRenderState")) {
          readAdditionalRenderState(statement.getContents());
        } else if (statType.equals("Transparent")) {
          readTransparentStatement(statement.getLine());
        }
      } else {
        if (statType.equals("Technique")) {
          readTechnique(statement);
        } else if (statType.equals("MaterialParameters")) {
          readMaterialParams(statement.getContents());
        } else {
          throw new MatParseException(
              "Expected material statement, got '" + statType + "'", statement);
        }
      }
    }
  }
  private void readTechnique(Statement techStat) throws IOException {
    isUseNodes = false;
    String[] split = techStat.getLine().split(whitespacePattern);

    String name;
    if (split.length == 1) {
      name = TechniqueDef.DEFAULT_TECHNIQUE_NAME;
    } else if (split.length == 2) {
      name = split[1];
    } else {
      throw new IOException("Technique statement syntax incorrect");
    }

    String techniqueUniqueName = materialDef.getAssetName() + "@" + name;
    technique = new TechniqueDef(name, techniqueUniqueName.hashCode());

    for (Statement statement : techStat.getContents()) {
      readTechniqueStatement(statement);
    }

    technique.setShaderPrologue(createShaderPrologue(presetDefines));

    switch (technique.getLightMode()) {
      case Disable:
        technique.setLogic(new DefaultTechniqueDefLogic(technique));
        break;
      case MultiPass:
        technique.setLogic(new MultiPassLightingLogic(technique));
        break;
      case SinglePass:
        technique.setLogic(new SinglePassLightingLogic(technique));
        break;
      case StaticPass:
        technique.setLogic(new StaticPassLightingLogic(technique));
        break;
      case SinglePassAndImageBased:
        technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique));
        break;
      default:
        throw new UnsupportedOperationException();
    }

    List<TechniqueDef> techniqueDefs = new ArrayList<>();

    if (isUseNodes) {
      nodesLoaderDelegate.computeConditions();

      // used for caching later, the shader here is not a file.

      // KIRILL 9/19/2015
      // Not sure if this is needed anymore, since shader caching
      // is now done by TechniqueDef.
      technique.setShaderFile(
          technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100");
      techniqueDefs.add(technique);
    } else if (shaderNames.containsKey(Shader.ShaderType.Vertex)
        && shaderNames.containsKey(Shader.ShaderType.Fragment)) {
      if (shaderLanguages.size() > 1) {
        for (int i = 1; i < shaderLanguages.size(); i++) {
          TechniqueDef td = null;
          try {
            td = (TechniqueDef) technique.clone();
          } catch (CloneNotSupportedException e) {
            e.printStackTrace();
          }
          td.setShaderFile(shaderNames, shaderLanguages.get(i));
          techniqueDefs.add(td);
        }
      }
      technique.setShaderFile(shaderNames, shaderLanguages.get(0));
      techniqueDefs.add(technique);

    } else {
      technique = null;
      shaderLanguages.clear();
      shaderNames.clear();
      presetDefines.clear();
      langSize = 0;
      logger.log(Level.WARNING, "Fixed function technique was ignored");
      logger.log(
          Level.WARNING,
          "Fixed function technique ''{0}'' was ignored for material {1}",
          new Object[] {name, key});
      return;
    }

    for (TechniqueDef techniqueDef : techniqueDefs) {
      materialDef.addTechniqueDef(techniqueDef);
    }

    technique = null;
    langSize = 0;
    shaderLanguages.clear();
    shaderNames.clear();
    presetDefines.clear();
  }
  private Texture parseTextureType(final VarType type, final String value) {
    final List<String> textureValues = tokenizeTextureValue(value);
    final List<TextureOptionValue> textureOptionValues = parseTextureOptions(textureValues);

    TextureKey textureKey = null;

    // If there is only one token on the value, it must be the path to the texture.
    if (textureValues.size() == 1) {
      textureKey = new TextureKey(textureValues.get(0), false);
    } else {
      String texturePath = value.trim();

      // If there are no valid "new" texture options specified but the path is split into several
      // parts, lets parse the old way.
      if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) {
        boolean flipY = false;

        if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) {
          texturePath = texturePath.substring(12).trim();
          flipY = true;
        } else if (texturePath.startsWith("Flip ")) {
          texturePath = texturePath.substring(5).trim();
          flipY = true;
        } else if (texturePath.startsWith("Repeat ")) {
          texturePath = texturePath.substring(7).trim();
        }

        // Support path starting with quotes (double and single)
        if (texturePath.startsWith("\"") || texturePath.startsWith("'")) {
          texturePath = texturePath.substring(1);
        }

        // Support path ending with quotes (double and single)
        if (texturePath.endsWith("\"") || texturePath.endsWith("'")) {
          texturePath = texturePath.substring(0, texturePath.length() - 1);
        }

        textureKey = new TextureKey(texturePath, flipY);
      }

      if (textureKey == null) {
        textureKey = new TextureKey(textureValues.get(textureValues.size() - 1), false);
      }

      // Apply texture options to the texture key
      if (!textureOptionValues.isEmpty()) {
        for (final TextureOptionValue textureOptionValue : textureOptionValues) {
          textureOptionValue.applyToTextureKey(textureKey);
        }
      }
    }

    switch (type) {
      case Texture3D:
        textureKey.setTextureTypeHint(Texture.Type.ThreeDimensional);
        break;
      case TextureArray:
        textureKey.setTextureTypeHint(Texture.Type.TwoDimensionalArray);
        break;
      case TextureCubeMap:
        textureKey.setTextureTypeHint(Texture.Type.CubeMap);
        break;
    }

    textureKey.setGenerateMips(true);

    Texture texture;

    try {
      texture = assetManager.loadTexture(textureKey);
    } catch (AssetNotFoundException ex) {
      logger.log(
          Level.WARNING, "Cannot locate {0} for material {1}", new Object[] {textureKey, key});
      texture = null;
    }

    if (texture == null) {
      texture = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager));
      texture.setKey(textureKey);
      texture.setName(textureKey.getName());
    }

    // Apply texture options to the texture
    if (!textureOptionValues.isEmpty()) {
      for (final TextureOptionValue textureOptionValue : textureOptionValues) {
        textureOptionValue.applyToTexture(texture);
      }
    }

    return texture;
  }