/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.groupingsection;

import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.exceptionparser.GroupingSectionException;
import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.notation.grouping.GroupingNotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * GroupingDetailedInformationParser class to parse detailed information about
 * the group
 *
 * @author hecht
 */
public class GroupingDetailedInformationParser implements State {
  private StateMachineParser _parser;

  private String details = "";

  private static final Logger LOG = LoggerFactory.getLogger(GroupingDetailedInformationParser.class);

  /**
   * Constructs with the state machine parser
   *
   * @param pParser current parser
   */
  public GroupingDetailedInformationParser(StateMachineParser pParser) {
    _parser = pParser;
  }

  /**
   * {@inheritDoc}
   *
   * @throws NotationException if notation is not valid
   */
  @Override
  public void doAction(char cha) throws GroupingSectionException, NotationException {
    /*
     * detailed information about the group is finished: annotation section
     * starts
     */
    if (cha == ')') {
      if (_parser.checkDetailsGroup(details)) {
        LOG.info("Group description is finished:");
        LOG.info("Transition to annotation section:");
        GroupingNotation current = _parser.notationContainer.getCurrentGroupingNotation();
        _parser.notationContainer.changeLastGroupingNotation(new GroupingNotation(current.getGroupID(), details));
        _parser.setState(new BetweenGroupingParser(_parser));
      } else {
        LOG.error("Group information is wrong: " + details);
        throw new GroupingSectionException("Group information is wrong: " + details);
      }
    } /* add characters to the details */ else {
      details += cha;
    }

  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.polymer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.exceptionparser.SimplePolymerSectionException;
import org.helm.notation2.parser.notation.ValidationMethod;
import org.jdom2.JDOMException;

/**
 * PolymerSingleElements class to represent a polymerelements for this class
 * only one monomer is allowed
 *
 * @author hecht
 */
public class PolymerSingleElements extends PolymerElements {

  public PolymerSingleElements() {

  }

  /**
   * Constructor
   * @param entity HELMEntity
   */
  public PolymerSingleElements(HELMEntity entity) {
    super(entity);
    this.listMonomerNotations = new ArrayList<MonomerNotation>();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public List<MonomerNotation> getListOfElements() {
    return this.listMonomerNotations;
  }

  /**
   * {@inheritDoc}
   *
   * @throws SimplePolymerSectionException if simple polymer section is not valid
   * @throws NotationException if not notation is not valid
   */
  @Override
  public void addMonomerNotation(String str) throws SimplePolymerSectionException, NotationException {
    if (this.listMonomerNotations.size() < 1) {
      this.listMonomerNotations.add(ValidationMethod.decideWhichMonomerNotation(str, entity.getType()));
    } else {
      throw new SimplePolymerSectionException("Only one Monomer unit is allowed for CHEM and BLOB");
    }

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public MonomerNotation getCurrentMonomerNotation() {
    return this.listMonomerNotations.get(0);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void changeMonomerNotation(MonomerNotation not) {
    this.listMonomerNotations.set(0, not);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return this.listMonomerNotations.get(0).toString();
  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.polymer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.helm.notation2.parser.exceptionparser.NotationException;
import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * MonomerNotationUnitRNA class to represent nucleotides
 *
 * @author hecht
 */
public class MonomerNotationUnitRNA extends MonomerNotationUnit {

  private List<MonomerNotationUnit> contents = new ArrayList<MonomerNotationUnit>();

  private int bracketCounterOpen = 0;

  private int bracketCounterClose = 0;

  private int parenthesisCounterOpen = 0;

  private int parenthesisCounterClose = 0;

  private List<String> contentsInformation = new ArrayList<String>();

  /**
   * @param str monomer notation
   * @param type of the monomer notation
   * @throws NotationException if notation is not valid
   */
  public MonomerNotationUnitRNA(String str, String type) throws NotationException {
    super(str, type);
    setRNAContents(str);
  }

  /**
   * method to set for each nucleotide the sugar, the base and the phosphat
   *
   * @param str monomer notation
   * @throws NotationException if  single monomers are not in brackets
   */
  private void setRNAContents(String str) throws NotationException {
    /* Nucleotide with all contents */
    String[] list;

    // if (str.contains("(")) {
    List<String> items = extractContents(str);
    for (String item : items) {
    	if(item.length()> 1){
    		if(!(item.startsWith("[") && item.endsWith("]"))){
    			throw new NotationException("Monomers have to be in brackets "+ item);
    		}
    	}
      contents.add(new MonomerNotationUnit(item, type));

    }

    // } /* nucleotide contains no base, but a modified sugar or phosphat */
    // else if (str.contains("[")) {
    // if (str.startsWith("[")) {
    // str = str.replace("]", "]$");
    // } else {
    // str = str.replace("[", "$[");
    // }
    // list = str.split("\\$");
    // for (int i = 0; i < list.length; i++) {
    // contents.add(new MonomerNotationUnit(list[i], type));
    // }
    // } /* nucleotide contains only standard sugar and/or phosphat */ else {
    // for (int i = 0; i < str.length(); i++) {
    // contents.add(new MonomerNotationUnit(Character.toString(str.charAt(i)),
    // type));
    // }
    // }

  }

  /**
   * method to get the contents of this nucleotide
   *
   * @return List of MonomerNotationUnits
   */
  public List<MonomerNotationUnit> getContents() {
    return this.contents;
  }

  private List<String> extractContents(String notation) throws NotationException {
    List<String> items = new ArrayList<String>();
    StringBuilder sb = new StringBuilder();
    for (char cha : notation.toCharArray()) {
      if (cha == '(') {
        if (!(checkBracketsParenthesis())) {
          sb.append(cha);
        } else {
          if (items.size() > 0) {
            contentsInformation.add("R");
          }
          contentsInformation.add("X");
        }
        parenthesisCounterOpen += 1;

      } else if (cha == ')') {
        parenthesisCounterClose += 1;
        if (checkBracketsParenthesis()) {
          items.add(sb.toString());
          sb = new StringBuilder();
        } else {
          sb.append(cha);
        }
      } else if (cha == '[') {
        bracketCounterOpen += 1;
        sb.append(cha);

      } else if (cha == ']') {
        bracketCounterClose += 1;
        sb.append(cha);
        if (checkBracketsParenthesis()) {
          items.add(sb.toString());
          sb = new StringBuilder();
        }
      } else {
        sb.append(cha);
        if (checkBracketsParenthesis()) {
          items.add(sb.toString());
          sb = new StringBuilder();
        }

      }
    }
    if (contentsInformation.size() == 0) {
      contentsInformation.add("R");
    }

    /* problem case */
    if (items.size() == 1) {

    }

    if (items.size() != contentsInformation.size()) {
      contentsInformation.add("P");
    }

    if(items.size()> 3){
    	throw new NotationException("RNA Unit is wrong " + notation);
    }
    return items;

  }

  /**
   * method to check if all open brackets are closed
   *
   * @return true if all open brackets are close, false otherwise
   */
  private boolean checkBracketsParenthesis() {
    if (bracketCounterOpen == bracketCounterClose && parenthesisCounterOpen == parenthesisCounterClose) {
      return true;
    }
    return false;
  }

  /**
   * method to get the contents of this nucleotide
   *
   * @return List of all
   */
  @JsonIgnore
  public List<String> getInformation() {
    return contentsInformation;
  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.connectionsection;

import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.exceptionparser.ConnectionSectionException;
import org.helm.notation2.parser.exceptionparser.ExceptionState;
import org.helm.notation2.parser.notation.connection.ConnectionNotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ConnectionsReadSecondIDParser class to parse the id of the second partner of
 * the connection
 *
 * @author hecht
 */
public class ConnectionsReadSecondIDParser implements State {
  private StateMachineParser _parser;

  private String targetpolymerid = "";

  private static final Logger LOG = LoggerFactory.getLogger(ConnectionsReadSecondIDParser.class);

  /**
   * Constructs with the state machine parser
   *
   * @param pParser current parser
   */
  public ConnectionsReadSecondIDParser(StateMachineParser pParser) {
    _parser = pParser;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void doAction(char cha) throws ExceptionState {

    /*
     * third part of the connection is starting. It contains information about
     * the connection in detail
     */
    if (cha == ',') {
      if (_parser.checkPolymeridConnection(targetpolymerid)) {
        LOG.info("Target polmyer ID is read:");
        ConnectionNotation current = _parser.notationContainer.getCurrentConnection();
        _parser.notationContainer.changeConnectionNotation(new ConnectionNotation(current.getSourceId(),
            targetpolymerid));
        _parser.setState(new ConnectionsDetailsParser(_parser));
      } else {
        LOG.error("Target polymer ID is not correct in the connection section: " + targetpolymerid);
        throw new ConnectionSectionException("Target polymer ID is not correct in the connection section: "
            + targetpolymerid);
      }
    } /* Read the id of the target polymer id */ else {
      targetpolymerid += cha;
    }
  }
}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.simplepolymerssection;

import org.helm.notation2.parser.InlineAnnotationsParser;
import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.connectionsection.ConnectionsParser;
import org.helm.notation2.parser.exceptionparser.SimplePolymerSectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BetweenParser class represents an interstate between a finished simple
 * polymer description and all possible states
 *
 * @author hecht
 */
public class BetweenParser implements State {

  private StateMachineParser _parser;

  private static final Logger LOG = LoggerFactory.getLogger(BetweenParser.class);

  /**
   * Constructs with the state macchine parser
   *
   * @param pParser StateMachineParser
   */
  public BetweenParser(StateMachineParser pParser) {
    _parser = pParser;

  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void doAction(char cha) throws SimplePolymerSectionException {

    /* an additional annotation is starting */
    if (cha == '\"') {
      LOG.info("Annotation for simple polymer is starting:");
      _parser.setState(new InlineAnnotationsParser(_parser, 1));
    } /* a new unit is starting */ else if (cha == '|') {
      LOG.info("One simple polymer is finished:");
      LOG.info("New simple polymer is starting:");
      _parser.setState(new SimplePolymersParser(_parser));
    } /* a new section is starting */ else if (cha == '$') {
      LOG.info("One simple polymer is finished:");
      LOG.info("Simple polymer section is finished:");
      LOG.info("Transition to connection section:");
      _parser.setState(new ConnectionsParser(_parser));
    } else {
      LOG.error("Invalid syntax in simple polymer section: " + cha);
      throw new SimplePolymerSectionException("Invalid syntax in simple polymer section: " + cha);
    }
  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.polymer;

import java.io.IOException;

import org.helm.notation2.parser.exceptionparser.HELM1ConverterException;
import org.helm.notation2.parser.exceptionparser.NotationException;

/**
 * MonomerNotationUnit
 *
 * @author hecht
 */
public class MonomerNotationUnit extends MonomerNotation {

  public MonomerNotationUnit() {

  }

  /**
   * Constructs with the given String
   *
   * @param str Monomer
   * @param type polymer type, e.g. RNA, Peptide or CHEM
   * @throws NotationException if notation is not valid
   */
  public MonomerNotationUnit(String str, String type) throws  NotationException {
    super(str, type);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toHELM2() {

    String text = unit;
    if (isDefault == false) {
      if (unit.length() > 1) {
        text = "(" + unit + ")";
      }
      text += "'" + count + "'";
    }

    if (isAnnotationHere) {
      text += "\"" + annotation + "\"";
    }
    return text;
  }

  @Override
  public String toHELM() throws HELM1ConverterException {

    String text = unit;

    /* check for appropriate units */
    if (!(isDefault)) {
      if (unit.length() > 1) {
        text = "(" + unit + ")";
      }
    }

    if (unit.equals("X") || unit.contains("(N)")) {
      throw new HELM1ConverterException("not possible to downgrade to HELM1 Format");
    }

    /*
     * convert the count in an appropriate way: throw an exception if it is not
     * an explicit integer value
     */
    try {
      int integerValue = Integer.parseInt(count);
      if (integerValue < 1 || integerValue > 1) {
        throw new HELM1ConverterException("Count of MonomerNotationUnit was not possible to downgrade");
      }
      // for (int i = 0; i < integer_value - 1; i++) {
      // text += "." + unit;
      // }
    } catch (NumberFormatException e) {
      throw new HELM1ConverterException("Count of MonomerNotationUnit was not possible to downgrade");
    }

    /* no annotation here!!! -> forget simple the annotation */

    return text;
  }

}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.groupingsection;

import org.helm.notation2.parser.State;
import org.helm.notation2.parser.StateMachineParser;
import org.helm.notation2.parser.annotationsection.AnnotationsParser;
import org.helm.notation2.parser.exceptionparser.GroupingSectionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BetweenGroupingParser class to represent the interstate between a finished
 * group and all possible states
 *
 * @author hecht
 */
public class BetweenGroupingParser implements State {
  private StateMachineParser _parser;

  private static final Logger LOG = LoggerFactory.getLogger(BetweenGroupingParser.class);

  /**
   * Constructs with the state machine parser
   *
   * @param pParser current parser
   */
  public BetweenGroupingParser(StateMachineParser pParser) {
    _parser = pParser;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void doAction(char cha) throws GroupingSectionException {
    /* a new group is starting */
    if (cha == '|') {
      LOG.info("A new group is starting;");
      _parser.setState(new GroupingParser(_parser));
    } /* group section is finished: start of the annotation section */ else if (cha == '$') {
      LOG.info("Group section is finished:");
      LOG.info("Transition to annotation section:");
      _parser.setState(new AnnotationsParser(_parser));

    } /* add characters to the grouping section */ else {
      LOG.error("Group section is not valid: " + cha);
      throw new GroupingSectionException("Group section is not valid: " + cha);
    }

  }
}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser;

import java.awt.Font;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UnsupportedLookAndFeelException;

import org.helm.notation2.parser.exceptionparser.ExceptionState;
import org.jdom2.JDOMException;

/**
 * HELMNotationParserUI class to show
 *
 * @author hecht
 */
public class HELMNotationParserUI extends JFrame {

  /**
   * Creates new form ContactEditorUI
   */
  public HELMNotationParserUI() {
    initComponents();
  }

  /**
   * This method is called from within the constructor to initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is always
   * regenerated by the Form Editor.
   */
  @SuppressWarnings("unchecked")
  private void initComponents() {

    jScrollPane1 = new javax.swing.JScrollPane();
    input = new javax.swing.JTextArea();
    inputLabel = new javax.swing.JLabel();
    outputLabel = new javax.swing.JLabel();
    jScrollPane2 = new javax.swing.JScrollPane();
    output = new javax.swing.JTextArea();
    jRadioHELM = new javax.swing.JRadioButton();
    jRadioJSON = new javax.swing.JRadioButton();
    jButton1 = new javax.swing.JButton();
    bg = new javax.swing.ButtonGroup();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setTitle("HELMNotationParser");
    input.setColumns(20);
    input.setRows(5);
    jScrollPane1.setViewportView(input);

    inputLabel.setText("HELM-Input:");
    inputLabel.setFont(new Font("Arial", Font.BOLD, 20));
    inputLabel.setHorizontalAlignment(JLabel.CENTER);
    inputLabel.setVerticalAlignment(JLabel.CENTER);

    outputLabel.setText("Output:");
    outputLabel.setFont(new Font("Arial", Font.BOLD, 20));
    outputLabel.setHorizontalAlignment(JLabel.CENTER);
    outputLabel.setVerticalAlignment(JLabel.CENTER);

    output.setColumns(20);
    output.setRows(5);
    jScrollPane2.setViewportView(output);

    jRadioHELM.setText("HELM2");
    jRadioHELM.addActionListener(new java.awt.event.ActionListener() {
      @Override
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jRadioButton1ActionPerformed(evt);
      }
    });

    jRadioJSON.setText("JSON");
    jRadioJSON.addActionListener(new java.awt.event.ActionListener() {
      @Override
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jRadioButton2ActionPerformed(evt);
      }
    });

    bg.add(jRadioHELM);
    bg.add(jRadioJSON);
    jRadioHELM.setSelected(true);
    jButton1.setText("Translate");
    jButton1.addActionListener(new java.awt.event.ActionListener() {
      @Override
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton1ActionPerformed(evt);
      }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false).addComponent(inputLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 438, Short.MAX_VALUE)).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addGap(31, 31, 31).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addComponent(jRadioHELM, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addComponent(jButton1).addComponent(jRadioJSON, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE).addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 458, javax.swing.GroupLayout.PREFERRED_SIZE)).addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup().addGap(132, 132, 132).addComponent(outputLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 458, javax.swing.GroupLayout.PREFERRED_SIZE))).addContainerGap()));
    layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addGap(0, 0, 0).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE).addComponent(inputLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 28, javax.swing.GroupLayout.PREFERRED_SIZE).addComponent(outputLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(layout.createSequentialGroup().addGap(9, 9, 9).addComponent(jRadioHELM).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED).addComponent(jRadioJSON).addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED).addComponent(jButton1).addContainerGap(778, Short.MAX_VALUE)).addComponent(jScrollPane1).addComponent(jScrollPane2))));

    pack();
  } // </editor-fold>

  private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
    String helmInput = input.getText();
    if (!(helmInput.contains("V2.0"))) {
      /* Translate into HELM2-format */
    	      helmInput = converter.doConvert(helmInput);

    }

    /* read input */
    try {
      parser.parse(helmInput);
      writeOutputmessage();
    } catch (ExceptionState e) {
      output.setText("Invalid HELM-String (" + e.getMessage() + ")");
    }

  }

  private void writeOutputmessage() {
    if (jRadioHELM.isSelected()) {
      output.setText(parser.getHELM2Notation().toHELM2());
    } else if (jRadioJSON.isSelected()) {
      output.setText(parser.getJSON());
    } else {
      output.setText("No Output-Option was specified");
    }

  }

  private void jRadioButton2ActionPerformed(java.awt.event.ActionEvent evt) {
    // TODO add your handling code here:
  }

  private void jRadioButton1ActionPerformed(java.awt.event.ActionEvent evt) {
    // TODO add your handling code here:
  }

  /**
   * @param args the command line arguments
   */
  public static void main(String args[]) {
    for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
      if ("Nimbus".equals(info.getName())) {
        try {
          javax.swing.UIManager.setLookAndFeel(info.getClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
            | UnsupportedLookAndFeelException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
        break;
      }
    }

    // </editor-fold>

    /* Create and display the form */
    java.awt.EventQueue.invokeLater(new Runnable() {
      @Override
      public void run() {
        new HELMNotationParserUI().setVisible(true);
      }
    });
  }

  // Variables declaration - do not modify
  private javax.swing.JButton jButton1;

  private javax.swing.JLabel inputLabel;

  private javax.swing.JLabel outputLabel;

  private javax.swing.JRadioButton jRadioHELM;

  private javax.swing.JRadioButton jRadioJSON;

  private javax.swing.ButtonGroup bg;

  private javax.swing.JScrollPane jScrollPane1;

  private javax.swing.JScrollPane jScrollPane2;

  private javax.swing.JTextArea input;

  private javax.swing.JTextArea output;

  private ConverterHELM1ToHELM2 converter = new ConverterHELM1ToHELM2();

  private ParserHELM2 parser = new ParserHELM2();
  // End of variables declaration
}


/**
 * *****************************************************************************
 * Copyright C 2015, The Pistoia Alliance
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *****************************************************************************
 */
package org.helm.notation2.parser.notation.grouping;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.helm.notation2.parser.exceptionparser.NotationException;
import org.helm.notation2.parser.notation.polymer.GroupEntity;

import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * GroupingNotation
 *
 * @author hecht
 */
public final class GroupingNotation {

  private GroupEntity groupID;

  private GroupingAmbiguity ambiguity;

  public GroupingNotation() {

  }

  /**
   * Constructs with the given String
   *
   * @param str Group ID
   * @throws NotationException if notation is not valid
   */
  public GroupingNotation(String str) throws NotationException {
	  Pattern p = Pattern.compile("G[1-9][0-9]*", Pattern.CASE_INSENSITIVE);
	  Matcher m = p.matcher(str);
    if (m.matches()) {
      this.groupID = new GroupEntity(str.toUpperCase());
    } else {
      throw new NotationException("Group Id is not correct: " + str);
    }
  }

  /**
   * Constructs with the GroupEntity and details about the group elements
   *
   * @param group GroupEntity
   * @param ambi details about the group elements
   * @throws NotationException if notation is not valid
   */
  public GroupingNotation(GroupEntity group, String ambi) throws NotationException {
    this.groupID = group;
    defineAmbiguity(ambi);
  }

  /**
   * method to get details about the group elements
   *
   * @return GroupingAmbiguity
   */
  public GroupingAmbiguity getAmbiguity() {
    return ambiguity;
  }

  /**
   * method to add ambiguity to the group
   *
   * @param a details about the group elements
   * @throws NotationException if notation is not valid
   */
  private void defineAmbiguity(String a) throws NotationException {
    Pattern patternAND = Pattern.compile("\\+");
    Matcher m = patternAND.matcher(a);

    /* mixture */
    if (m.find()) {
      setAmbiguity(new GroupingMixture(a));
    } /* or case */ else {
      setAmbiguity(new GroupingOr(a));
    }

  }

  private void setAmbiguity(GroupingAmbiguity a) {
    ambiguity = a;
  }

  /**
   * method to get the groupID
   *
   * @return groupID
   */
  public GroupEntity getGroupID() {
    return groupID;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String toString() {
    return "GroupID: " + groupID + " " + ambiguity.toString();
  }

  /**
   * method to get a valid HELM2 notation of the group
   *
   * @return valid HELM2 notation
   */
  public String toHELM2() {
    return groupID + "(" + ambiguity.toHELM2() + ")";
  }

}


/**
 * Copyright 2015-2016 Red Hat, Inc, and individual contributors.
 *
 * 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 org.wildfly.swarm.topology.consul;

import java.net.MalformedURLException;
import java.net.URL;

import org.wildfly.swarm.spi.api.Fraction;
import org.wildfly.swarm.spi.api.StageConfig;
import org.wildfly.swarm.spi.api.SwarmProperties;

/**
 * Consul topology-management fractoin.
 *
 * This fraction allows for the use of a cluster of Consul servers and agents
 * to manage topology information.
 *
 * @author John Hovell
 * @author Bob McWhirter
 */
public class ConsulTopologyFraction implements Fraction {

    /**
     * Construct a default fraction using the default agent URL of http://localhost:8500/.
     */
    @Override
    public void initialize(InitContext initContext) {
        if(initContext.projectStage().isPresent()) {
            try {
                StageConfig stageConfig = initContext.projectStage().get();
                String configvalue = stageConfig.resolve(SwarmProperties.CONSUL_URL)
                        .withDefault(null)
                        .getValue();
                this.url = configvalue !=null ? new URL(configvalue):DEFAULT_URL;
            } catch (MalformedURLException e) {
                throw new RuntimeException("Faile to resolve property 'swarm.consul.url'", e);
            }
        }
    }

    public ConsulTopologyFraction() {
        this(DEFAULT_URL);
    }

    /**
     * Construct with an agent URL
     *
     * @param url Agent URL
     */
    public ConsulTopologyFraction(URL url) {
        url(url);
    }

    /**
     * Construct with an agent URL
     *
     * @param url Agent URL
     */
    public ConsulTopologyFraction(String url) throws MalformedURLException {
        url(url);
    }

    /**
     * Set the agent URL
     *
     * @param url The agent URL.
     * @return this fraction.
     */
    public ConsulTopologyFraction url(URL url) {
        this.url = url;
        return this;
    }

    /**
     * Set the agent URL
     *
     * @param url The agent URL.
     * @return this fraction
     * @throws MalformedURLException if an error occurs parsing the URL.
     */
    public ConsulTopologyFraction url(String url) throws MalformedURLException {
        this.url = new URL(url);
        return this;
    }

    /**
     * Retrieve the configured agent URL.
     *
     * @return The agent URL.
     */
    public URL url() {
        return this.url;
    }

    /**
     * The default consul Agent URL (http://localhost:8500/)
     */
    private static final URL DEFAULT_URL;

    static {
        URL tmp = null;
        try {
            String consulUrl = System.getProperty(SwarmProperties.CONSUL_URL, "http://localhost:8500/");
            tmp = new URL(consulUrl);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        DEFAULT_URL = tmp;
    }

    private URL url = null;

}


package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.function.AStatement1NoThrow;
import com.ajjpj.afoundation.util.AUnchecker;
import sun.misc.Contended;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author arno
 */
@Contended
class WorkerThread extends Thread {
    long a1, a2, a3, a4, a5, a6, a7;

    final LocalQueue localQueue;                 // accessed only from this thread
    private final ASharedQueue[] sharedQueues;   // accessed only from this thread
    private final LocalQueue[] allLocalQueues;   // accessed only from this thread
    final AThreadPoolImpl pool;                  // accessed only from this thread
    private final int queueTraversalIncrement;   // accessed only from this thread
    private final AStatement1NoThrow<Throwable> exceptionHandler; // accessed only from this thread
    private final int ownLocalFifoInterval;      // accessed only from this thread
    private final int skipLocalWorkInterval;     // accessed only from this thread
    private final int switchSharedQueueInterval; // accessed only from this thread

    /**
     * Processing the 'top', i.e. LIFO, element of a thread's local queue is typically desirable because caches tend to still be
     *  hot. It can however lead to starvation with 'old' work never getting done in very specific (pretty pathological) load
     *  scenarios where every work item spawns a new work item. To avoid these starvation scenarios, a WorkerThread reads from
     *  the bottom of its local queue once in a while.
     */
    private final int numPrefetchLocal;        // accessed only from this thread

    final long idleThreadMask;                 //accessed from arbitrary other thread during thread wake-up

    long p1, p2, p3, p4, p5, p6, p7;

    //---------------------------------------------------------------------------------------------------------
    //-- all mutable state is below this point; it is accessed (both read and write) only from this thread.
    //---------------------------------------------------------------------------------------------------------

    long stat_numTasksExecuted = 0;
    long stat_numSharedTasksExecuted = 0;
    long stat_numSteals = 0;
    long stat_numExceptions = 0;

    long stat_numParks = 0;
    long stat_numFalseAlarmUnparks = 0;
    long stat_numSharedQueueSwitches = 0;

    long stat_numLocalSubmits = 0;

    /**
     * This is the index of the shared queue that this thread currently feeds from.
     */
    private int currentSharedQueue = 0;

    private int localIntermittentFifoCounter = 0;
    private int skipLocalWorkCounter = 0;
    private int switchSharedQueueCounter = 0;

    long q1, q2, q3, q4, q5, q6, q7;

    WorkerThread (int ownLocalFifoInterval, int skipLocalWorkInterval, int switchSharedQueueInterval, int numPrefetchLocal, LocalQueue localQueue, ASharedQueue[] sharedQueues, AThreadPoolImpl pool, int threadIdx, int queueTraversalIncrement, AStatement1NoThrow<Throwable> exceptionHandler) {
        this.ownLocalFifoInterval = ownLocalFifoInterval;
        this.skipLocalWorkInterval = skipLocalWorkInterval;
        this.switchSharedQueueInterval = switchSharedQueueInterval;

        this.localIntermittentFifoCounter = ownLocalFifoInterval;
        this.skipLocalWorkCounter = skipLocalWorkInterval;
        this.switchSharedQueueCounter = switchSharedQueueInterval;

        this.numPrefetchLocal = numPrefetchLocal;
        this.exceptionHandler = exceptionHandler;

        this.localQueue = localQueue;
        this.sharedQueues = sharedQueues;
        this.pool = pool;

        this.allLocalQueues = pool.localQueues;
        idleThreadMask = 1L << threadIdx;
        this.queueTraversalIncrement = queueTraversalIncrement;

        currentSharedQueue = threadIdx % sharedQueues.length;
    }

    /**
     * This method returns an approximation of this thread's execution statistics for the entire period since the thread was started. Writes are done without memory barriers
     *  to minimize the performance impact of statistics gathering, so some or all returned data may be arbitrarily stale, and some fields may be far staler than others. For
     *  long-running pools however even approximate data may provide useful insights. Your mileage may vary however, you have been warned ;-)
     */
    AWorkerThreadStatistics getStatistics() {
        return new AWorkerThreadStatistics (getState (), getId (),
                stat_numTasksExecuted, stat_numSharedTasksExecuted, stat_numSteals, stat_numExceptions, stat_numParks, stat_numFalseAlarmUnparks, stat_numSharedQueueSwitches, stat_numLocalSubmits, localQueue.approximateSize ());
    }

    @Override public void run () {
        long tasksAtPark = -1;

//        topLevelLoop:
        while (true) {
            try {
                Runnable task;

                if ((task = tryGetWork ()) != null) {
                    if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numTasksExecuted += 1;
                    task.run ();
                }
                else {
                    // spin a little before parking - this currently does not provide measurable speedup
//                    for (int i=0; i<0; i++) {
//                        if ((task = tryGetForeignWork ()) != null) {
//                            if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numTasksExecuted += 1;
//                            task.run ();
//                            continue topLevelLoop;
//                        }
//                    }

                    pool.markWorkerAsIdle (idleThreadMask);

                    // re-check availability of work after marking the thread as idle --> avoid races
                    if ((task = tryGetForeignWork ()) != null) {
                        if (! pool.markWorkerAsBusy (idleThreadMask)) {
                            // thread was 'woken up' because of available work --> cause some other thread to be notified instead
                            pool.unmarkScanning (); //TODO merge with 'markWorkerAsBusy'
                            pool.onAvailableTask ();
                        }
                        if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numTasksExecuted += 1;
                        task.run ();
                        continue;
                    }

                    if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numParks += 1;
                    if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) {
                        if (tasksAtPark == stat_numTasksExecuted) {
                            stat_numFalseAlarmUnparks += 1;
                        }
                        else {
                            tasksAtPark = stat_numTasksExecuted;
                        }
                    }

                    UNSAFE.park (false, 0L);

                    // This flag is usually set before the call unpark(), but some races cause a thread to be unparked redundantly, causing the flag to be out of sync.
                    // Setting the flag before unpark() is piggybacked on another CAS operation and therefore basically for free, so we leave it there, but we need it
                    //  here as well.
                    pool.markWorkerAsBusy (idleThreadMask);

                    if ((task = tryGetForeignWork ()) != null) {
                        pool.unmarkScanning();
                        pool.wakeUpWorker ();
                        if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numTasksExecuted += 1;
                        task.run ();
                    }
                    else {
                        pool.unmarkScanning();
                    }
                }
            }
            catch (PoolShutdown e) {
                e.shutdownFuture.completeAsSuccess (null);
                return;
            }
            catch (Throwable th) {
                if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numExceptions += 1;
                //TODO special handling for 'important' errors and exceptions?
                try {
                    exceptionHandler.apply (th);
                }
                catch (Throwable th2) {
                    System.err.println ("exception handler terminated with a throwable");
                    th2.printStackTrace ();
                }
            }
        }
    }

    private Runnable tryGetWork() {
        Runnable task;

        if ((task = getOwnWork ()) != null) {
            return task;
        }
        else if ((task = tryGetSharedWork ()) != null) {
            return task;
        }
        else if ((task = tryStealWork ()) != null) {
            return task;
        }
        return null;
    }

    private Runnable getOwnWork() {
        if (skipLocalWorkCounter == 0) {
            skipLocalWorkCounter = skipLocalWorkInterval;
            return null;
        }
        skipLocalWorkCounter -= 1;

        if (localIntermittentFifoCounter == 0) {
            localIntermittentFifoCounter = ownLocalFifoInterval;
            return localQueue.popFifo();
        }
        else {
            final Runnable task = localQueue.popLifo();
            if (task != null) {
                localIntermittentFifoCounter -= 1;
            }
            else {
                localIntermittentFifoCounter = ownLocalFifoInterval;
            }
            return task;
        }
    }

    private Runnable tryGetForeignWork () {
        Runnable task;

        if ((task = tryGetSharedWork ()) != null) {
            return task;
        }
        else if ((task = tryStealWork ()) != null) {
            return task;
        }
        return null;
    }

    private Runnable tryGetSharedWork() {
        Runnable task;

        if (switchSharedQueueCounter == 0) {
            // change the queue once in a while
            switchSharedQueueCounter = switchSharedQueueInterval;
            currentSharedQueue = (currentSharedQueue + queueTraversalIncrement) % sharedQueues.length;
        }

        final int prevQueue = currentSharedQueue;

        //noinspection ForLoopReplaceableByForEach
        for (int i=0; i < sharedQueues.length; i++) {
            if ((task = sharedQueues[currentSharedQueue].popFifo (localQueue)) != null) { //TODO adjust statistics to reflect prefetched tasks
                if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numSharedTasksExecuted += 1;
                //noinspection PointlessBooleanExpression,ConstantConditions
                if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS && prevQueue != currentSharedQueue) stat_numSharedQueueSwitches += 1;

                switchSharedQueueCounter -= 1;
                return task;
            }
            currentSharedQueue = (currentSharedQueue + queueTraversalIncrement) % sharedQueues.length;
        }

        return null;
    }

    private Runnable tryStealWork () {
        Runnable task;
        for (LocalQueue otherQueue: allLocalQueues) {
            if (otherQueue == localQueue) {
                continue;
            }
            if ((task = otherQueue.popFifo ()) != null) {
                if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numSteals += 1;

                //TODO refine prefetching based on other queue's size
                //TODO other LocalQueue implementations
                for (int i=0; i<numPrefetchLocal; i++) {
                    final Runnable prefetched = otherQueue.popFifo ();
                    if (prefetched == null) break;
                    localQueue.push (prefetched);
                    if (AThreadPoolImpl.SHOULD_GATHER_STATISTICS) stat_numSteals += 1; //TODO count separately?
                }
                return task;
            }
        }

        return null;
    }

    //-------------------- Unsafe stuff
    private static final Unsafe UNSAFE;

    static {
        try {
            final Field f = Unsafe.class.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            UNSAFE = (Unsafe) f.get (null);
        }
        catch (Exception exc) {
            AUnchecker.throwUnchecked (exc);
            throw new RuntimeException(); // for the compiler
        }
    }
}


package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.util.AUnchecker;
import sun.misc.Contended;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author arno
 */
@Contended
class SharedQueueNonblockPushBlockPopImpl implements ASharedQueue {
    long p1, p2, p3, p4, p5, p6, p7;

    private final int prefetchBatchSize;
    /**
     * an array holding all currently submitted tasks.
     */
    private final Runnable[] tasks;

    /**
     * a bit mask to project an offset into the valid range of offsets for the tasks array
     */
    private final int mask;

    private final AThreadPoolImpl pool;

    private long base = 0;
    private long top = 0;

    /**
     * This variable serves as a spin lock. All access goes through UNSAFE.
     */
    @SuppressWarnings ("UnusedDeclaration")
    private int lock = 0;

    long q1, q2, q3, q4, q5, q6, q7;

    SharedQueueNonblockPushBlockPopImpl (int prefetchBatchSize, AThreadPoolImpl pool, int size) {
        if (prefetchBatchSize < 1) throw new IllegalArgumentException ("worker threads must (attempt to) fetch a minimum of 1 task");
        this.prefetchBatchSize = prefetchBatchSize;
        this.pool = pool;

        if (1 != Integer.bitCount (size)) throw new IllegalArgumentException ("size must be a power of 2");
        if (size < 8 || size > 1024*1024) throw new IllegalArgumentException ("size must be in the range from 8 to " + (1024*1024));

        this.tasks = new Runnable[size];
        this.mask = size-1;
    }

    /**
     * @return an approximation of the queue's current size. The value may be stale and is not synchronized in any way, and it is intended for debugging and statistics
     *  purposes only.
     */
    @Override public int approximateSize () {
        return (int) (
                UNSAFE.getLongVolatile (this, OFFS_TOP) -
                UNSAFE.getLongVolatile (this, OFFS_BASE)
        );
    }

    /**
     * Add a new task to the top of the shared queue, incrementing 'top'.
     */
    @Override public void push (Runnable task) {
        lock ();

        final long _base;
        final long _top;
        try {
            _base = UNSAFE.getLongVolatile (this, OFFS_BASE);
            _top = UNSAFE.getLongVolatile (this, OFFS_TOP);

            if (_top == _base + mask) {
                throw new RejectedExecutionExceptionWithoutStacktrace ("Shared queue overflow");
            }

            // We hold a lock here, so there can be no concurrent modifications of 'top', and there is no need for CAS. We must however ensure that
            //  no concurrently reading thread sees the incremented 'top' without the task being present in the array, therefore the 'ordered' put.
            UNSAFE.putObjectVolatile (tasks, taskOffset (_top), task);
            UNSAFE.putLongVolatile (this, OFFS_TOP, _top+1);
        }
        finally {
            unlock ();
        }

        pool.onAvailableTask ();
    }

    @Override public synchronized Runnable popFifo (LocalQueue localQueue) {
        final long _base = base;
        final long _top = top;

        final long size = _top-_base;

        if (size == 0) {
            // Terminate the loop: the queue is empty.
            //TODO verify that Hotspot optimizes this kind of return-from-the-middle well
            return null;
        }

        final Runnable result = fetchTask (_base);
        if (result == null) {
            System.err.println ("null @ " + _base);
            return null; //TODO why is this necessary?
        }

        int idx;

        long newLocalTop = localQueue.top;
        for (idx=1; idx < size && idx < prefetchBatchSize; idx++) {
            final Runnable task = fetchTask (_base+idx);
            if (task == null) {
                System.err.println ("************************ fetched task[base+" + idx + "] is null although it really couldn't *******************************");
            }

            localQueue.tasks [localQueue.asArrayindex (newLocalTop++)] = task;
        }

        // volatile put for atomicity and to ensure ordering wrt. nulling the task --> read operations do not hold the same monitor
        if (idx > 1) {
            UNSAFE.putLongVolatile (localQueue, LocalQueue.OFFS_TOP, newLocalTop);
        }

        // volatile put for atomicity and to ensure ordering wrt. nulling the task --> read operations do not hold the same monitor
        UNSAFE.putLongVolatile (this, OFFS_BASE, _base + idx);

        return result;
    }

    private Runnable fetchTask (long idx) {
        final int arrIdx = asArrayIndex (idx);
        final Runnable result = tasks [arrIdx];
        if (result != null) tasks[arrIdx] = null; //TODO remove the check
        return result;
    }

    @Override public synchronized void clear () {
        final long _top = top;
        for (long _base=base; _base < _top; _base++) {
            tasks[asArrayIndex (_base)] = null;
        }

        UNSAFE.putLongVolatile (this, OFFS_BASE, _top);
    }

    private void lock() {
        //noinspection StatementWithEmptyBody
        while (!tryLock ()); //TODO add 'pause' when that becomes available
    }

    private boolean tryLock() {
        return UNSAFE.compareAndSwapInt (this, OFFS_LOCK, 0, 1);
    }

    private void unlock() {
        UNSAFE.putIntVolatile (this, OFFS_LOCK, 0);
    }

    private long taskOffset (long l) {
        return OFFS_TASKS + SCALE_TASKS * (l & mask);
    }

    private int asArrayIndex (long l) {
        return (int) (l & mask);
    }

    //------------- Unsafe stuff
    private static final Unsafe UNSAFE;

    private static final long OFFS_TASKS;
    private static final long SCALE_TASKS;

    private static final long OFFS_LOCK;
    private static final long OFFS_BASE;
    private static final long OFFS_TOP;

    static {
        try {
            final Field f = Unsafe.class.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            UNSAFE = (Unsafe) f.get (null);

            OFFS_TASKS = UNSAFE.arrayBaseOffset (Runnable[].class);
            SCALE_TASKS = UNSAFE.arrayIndexScale (Runnable[].class);

            OFFS_LOCK = UNSAFE.objectFieldOffset (SharedQueueNonblockPushBlockPopImpl.class.getDeclaredField ("lock"));
            OFFS_BASE = UNSAFE.objectFieldOffset (SharedQueueNonblockPushBlockPopImpl.class.getDeclaredField ("base"));
            OFFS_TOP  = UNSAFE.objectFieldOffset (SharedQueueNonblockPushBlockPopImpl.class.getDeclaredField ("top"));
        }
        catch (Exception e) {
            AUnchecker.throwUnchecked (e);
            throw new RuntimeException(); // for the compiler
        }
    }
}


package com.ajjpj.afoundation.collection.graph;

import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.collection.immutable.AHashMap;
import com.ajjpj.afoundation.collection.immutable.AList;
import com.ajjpj.afoundation.collection.immutable.AMap;
import com.ajjpj.afoundation.function.APredicateNoThrow;

import java.io.Serializable;
import java.util.*;

/**
 * This class represents a directed graph - in the sense of a data structure, not of its visual representation. It is
 *  immutable, i.e. its nodes and edges are fixed on initialization. It is thread safe, i.e. it is safe to access concurrently
 *  from several threads.

 *
 * A graph is represented as a collection of nodes and a second collection of edges. There is no requirement for the graph to be
 *  connected.

 *
 * This class strikes a balance between memory footprint and speed. It lazily computes and then caches results that are deemed
 *  likely to be reused.
 *
 * @author arno
 */
public class ADiGraph<N,E extends AEdge<N>> implements Serializable {
    private final Object LOCK = new Object ();

    private final Object[] nodes;
    private final AEdge[] edges;

    /**
     * This is a convenience factory method that extracts the list of nodes from the edges. It assumes that every node has at least one
     *  edge going from or to it.
     */
    public static <N, E extends AEdge<N>> ADiGraph<N,E> create (Collection<E> edges) {
        final Set<N> result = new HashSet<> ();
        for (E edge: edges) {
            result.add (edge.getFrom ());
            result.add (edge.getTo ());
        }

        return create (result, edges);
    }

    /**
     * This factory method creates a graph with the given nodes and edges. It expressly allows nodes that have no edges attached to them.
     */
    public static <N, E extends AEdge<N>> ADiGraph<N,E> create (Collection<N> nodes, Collection<E> edges) {
        final Object[] nodeArr = new Object[nodes.size ()];
        final AEdge[] edgeArr = new AEdge[edges.size ()];

        int idx = 0;
        for (N node: nodes) {
            nodeArr[idx] = node;
            idx += 1;
        }

        idx = 0;
        for (E edge: edges) {
            edgeArr[idx] = edge;
            idx += 1;
        }

        return new ADiGraph<N,E> (nodeArr, edgeArr);
    }

    private ADiGraph (Object[] nodes, AEdge[] edges) {
        this.nodes = nodes;
        this.edges = edges;
    }

    /**
     * @return this graph's nodes
     */
    public Collection<N> nodes () {
        return new ArrayIterable<> (nodes);
    }

    /**
     * @return this graph's edges
     */
    public Collection<E> edges () {
        return new ArrayIterable<> (edges);
    }

    //TODO helper: create list of edges from 'partial order'

    private transient AMap<N,AList<AEdgePath<N,E>>> _incomingPathsInternal;
    private transient AMap<N,AList<AEdgePath<N,E>>> _outgoingPathsInternal;
    private transient AList<AEdgePath<N,E>> _cyclesInternal;

    /**
     * This method does the reachability analysis in a way that is useful for many other methods.
     */
    private void initPathsInternal() {
        synchronized (LOCK) {
            if (_incomingPathsInternal == null) {
                AMap<N, AList<AEdgePath<N, E>>> incomingPaths = AHashMap.empty ();
                //noinspection unchecked
                incomingPaths = incomingPaths.withDefaultValue (AList.nil);

                AMap<N, AList<AEdgePath<N, E>>> outgoingPaths = AHashMap.empty ();
                //noinspection unchecked
                outgoingPaths = outgoingPaths.withDefaultValue (AList.nil);

                AList<AEdgePath<N, E>> cycles = AList.nil ();

                for (N curNode : nodes ()) { // iterate over nodes, treat 'curNode' as a target
                    final Iterable<E> curIncoming = incomingEdges (curNode);
                    List<AEdgePath<N, E>> unfinishedBusiness = new ArrayList<> ();
                    for (E incomingEdge : curIncoming) {
                        unfinishedBusiness.add (AEdgePath.create (incomingEdge));
                    }

                    AList<AEdgePath<N, E>> nonCycles = AList.nil ();

                    while (unfinishedBusiness.size () > 0) {
                        final List<AEdgePath<N, E>> curBusiness = unfinishedBusiness;

                        for (AEdgePath<N, E> p : unfinishedBusiness) {
                            if (!p.hasCycle () || p.isMinimalCycle ()) nonCycles = nonCycles.cons (p);
                            if (p.isMinimalCycle ()) cycles = cycles.cons (p);
                        }

                        unfinishedBusiness = new ArrayList<> ();

                        for (AEdgePath<N, E> curPath : curBusiness) {
                            final Iterable<E> l = incomingEdges (curPath.getFrom ());
                            for (E newEdge : l) {
                                final AEdgePath<N, E> pathCandidate = curPath.prepend (newEdge);
                                if (!pathCandidate.hasNonMinimalCycle ()) {
                                    unfinishedBusiness.add (pathCandidate);
                                }
                            }
                        }

                    }
                    incomingPaths = incomingPaths.updated (curNode, nonCycles);

                    for (AEdgePath<N, E> p : nonCycles) {
                        outgoingPaths = outgoingPaths.updated (p.getFrom (), outgoingPaths.getRequired (p.getFrom ()).cons (p));
                    }
                }
                _incomingPathsInternal = incomingPaths;
                _outgoingPathsInternal = outgoingPaths;
                _cyclesInternal = cycles;
            }
        }
    }

    private transient Map<N,List<E>> _incomingEdges = null;
    private Map<N,List<E>> incomingEdges() {
        if (_incomingEdges == null) {
            _incomingEdges = new HashMap<> ();

            for (E edge: edges ()) {
                List<E> edgeList = _incomingEdges.get (edge.getTo ());
                if (edgeList == null) {
                    edgeList = new ArrayList<> ();
                    _incomingEdges.put (edge.getTo (), edgeList);
                }
                edgeList.add (edge);
            }
        }

        return _incomingEdges;
    }

    /**
     * @return all edges having the given node as their target.
     */
    public Collection<E> incomingEdges (N node) {
        final List<E> result = incomingEdges ().get (node);
        return result != null ? Collections.unmodifiableList (result) : Collections.<E>emptyList ();
    }

    private transient Map<N,List<E>> _outgoingEdges = null;
    private Map<N,List<E>> outgoingEdges() {
        if (_outgoingEdges == null) {
            _outgoingEdges = new HashMap<> ();

            for (E edge: edges ()) {
                List<E> edgeList = _outgoingEdges.get (edge.getFrom ());
                if (edgeList == null) {
                    edgeList = new ArrayList<> ();
                    _outgoingEdges.put (edge.getFrom (), edgeList);
                }
                edgeList.add (edge);
            }
        }

        return _outgoingEdges;
    }

    /**
     * @return all edges having the given node as their source.
     */
    public Collection<E> outgoingEdges (N node) {
        final List<E> result = outgoingEdges ().get (node);
        return result != null ? Collections.unmodifiableList (result) : Collections.<E>emptyList ();
    }

    /**
     * @return all paths having this node as their source - more specifically, all paths that are either acyclic or minimal cycles.
     */
    public Collection<AEdgePath<N,E>> outgoingPaths (N node) {
        initPathsInternal ();
        return _outgoingPathsInternal.getRequired (node).asJavaUtilList ();
    }

    /**
     * @return all paths having this node as their target - more specifically, all paths that are either acyclic or minimal cycles.
     */
    public Collection<AEdgePath<N,E>> incomingPaths (N node) {
        initPathsInternal ();
        return _incomingPathsInternal.getRequired (node).asJavaUtilList ();
    }

    /**
     * @return true if and only if there is an edge from {@code from} to {@code to}.
     */
    public boolean hasEdge (N from, N to) {
        for (E edge: incomingEdges (to)) {
            if (from.equals (edge.getFrom ())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return true if and only if there is a path from {@code from} to {@code to}.
     */
    public boolean hasPath (N from, N to) {
        for (AEdgePath<N,E> path: incomingPaths (to)) {
            if (from.equals (path.getFrom ())) {
                return true;
            }
        }
        return false;
    }

    /**
     * A directed graph defines a partial order through 'reachability', and this method sorts the graph's nodes based on that
     *  partial order.
     *
     * @return all nodes, sorted in such a way that a node is guaranteed to come before all nodes that can be reached from it.
     * @throws AGraphCircularityException if the graph has cycles and can therefore not be sorted by reachability
     */
    public List<N> sortedNodesByReachability() throws AGraphCircularityException {
        if (hasCycles()) {
            throw new AGraphCircularityException ();
        }

        final Object[] result = new Object[nodes.length];
        int nextIdx = 0;

        final Set<N> unprocessed = new HashSet<> ();
        for (Object node: nodes) {
            //noinspection unchecked
            unprocessed.add ((N) node);
        }

        //TODO Map<N,Integer> with 'remaining' incoming edges, decrement when a node is 'processed' --> JMH

        while (! unprocessed.isEmpty ()) {
            final Set<N> nextBatch = ACollectionHelper.filter (unprocessed, new APredicateNoThrow<N> () {
                @Override public boolean apply (N n) {
                    for (E e : incomingEdges (n)) {
                        if (unprocessed.contains (e.getFrom ())) {
                            return false;
                        }
                    }
                    return true;
                }
            });

            unprocessed.removeAll (nextBatch);
            for (N n: nextBatch) {
                result[nextIdx] = n;
                nextIdx += 1;
            }
        }

        return new ArrayIterable<> (result);
    }

    /**
     * @return all paths in the graph that are minimal cycles, i.e. paths with the same start and end point and in which every node appears exactly once as a start and end point.
     */
    public Collection<AEdgePath<N,E>> minimalCycles () {
        initPathsInternal ();
        return _cyclesInternal.asJavaUtilCollection ();
    }

    /**
     * @return true if and only if the graph contains cycles
     */
    public boolean hasCycles() {
        return ! minimalCycles ().isEmpty ();
    }

    /**
     * @return true if and only if the graph does not contain cycles
     */
    public boolean isAcyclic() {
        return minimalCycles ().isEmpty ();
    }

    /**
     * @return true if and only if every node has at most one edge pointing at it, and there is exactly one root node.
     */
    public boolean isTree() {
        return isForest () && rootNodes ().size() == 1;
    }

    private Boolean _isForest;
    /**
     * @return true if and only if every node has at most one edge pointing at it.
     */
    public boolean isForest() {
        synchronized (LOCK) {
            if (_isForest == null) {
                _isForest = !hasCycles () && ACollectionHelper.forAll (nodes (), new APredicateNoThrow<N> () {
                    @Override public boolean apply (N n) {
                        return incomingEdges (n).size () <= 1; // no node with more than one edge pointing to it
                    }
                });
            }
        }
        return _isForest;
    }

    private transient Collection<N> _rootNodes;
    /**
     * @return all nodes with no edges pointing at them.
     */
    public Collection<N> rootNodes() {
        synchronized (LOCK) {
            if (_rootNodes == null) {
                _rootNodes = ACollectionHelper.filter (nodes (), new APredicateNoThrow<N> () {
                    @Override public boolean apply (N n) {
                        return incomingEdges (n).isEmpty ();
                    }
                });
            }
            return _rootNodes;
        }
    }

    private transient Collection<N> _leafNodes;
    /**
     * @return all nodes with no edge pointing from them.
     */
    public Collection<N> leafNodes() {
        synchronized (this) {
            if (_leafNodes == null) {
                _leafNodes = ACollectionHelper.filter (nodes (), new APredicateNoThrow<N> () {
                    @Override public boolean apply (N n) {
                        return outgoingEdges (n).isEmpty ();
                    }
                });
            }
            return _leafNodes;
        }
    }

    private static class ArrayIterable<T> extends AbstractList<T> {
        private final Object[] data;

        ArrayIterable (Object[] data) {
            this.data = data;
        }

        @SuppressWarnings ("NullableProblems")
        @Override public Iterator<T> iterator () {
            return new ArrayIterator<> (data);
        }

        @SuppressWarnings ("unchecked")
        @Override public T get (int index) {
            return (T) data[index];
        }
        @Override public int size () {
            return data.length;
        }
    }

    private static class ArrayIterator<T> implements Iterator<T> {
        private final Object[] data;
        private int nextIdx = 0;

        public ArrayIterator (Object[] data) {
            this.data = data;
        }

        @Override public boolean hasNext () {
            return nextIdx < data.length;
        }
        @SuppressWarnings ("unchecked")
        @Override public T next () {
            final T result = (T) data[nextIdx];
            nextIdx += 1;
            return result;
        }
        @Override public void remove () {
            throw new UnsupportedOperationException ();
        }
    }
}