refactoring
This commit is contained in:
parent
160e12a8bd
commit
d99106560d
|
@ -3,7 +3,7 @@ package com.rusefi;
|
|||
import com.devexperts.logging.Logging;
|
||||
import com.opensr5.ini.field.EnumIniField;
|
||||
import com.rusefi.core.Pair;
|
||||
import com.rusefi.output.ConfigStructure;
|
||||
import com.rusefi.output.ConfigStructureImpl;
|
||||
import com.rusefi.output.DataLogConsumer;
|
||||
import com.rusefi.output.JavaFieldsConsumer;
|
||||
|
||||
|
@ -115,7 +115,7 @@ public class ConfigField {
|
|||
return Integer.parseInt(s);
|
||||
}
|
||||
|
||||
public ConfigStructure getStructureType() {
|
||||
public ConfigStructureImpl getStructureType() {
|
||||
return getState().getStructures().get(getType());
|
||||
}
|
||||
|
||||
|
|
|
@ -33,10 +33,10 @@ public class ReaderState implements IReaderState {
|
|||
private static final String STRUCT = "struct ";
|
||||
// used to update other files
|
||||
private final List<String> inputFiles = new ArrayList<>();
|
||||
private final Stack<ConfigStructure> stack = new Stack<>();
|
||||
private final Stack<ConfigStructureImpl> stack = new Stack<>();
|
||||
private final Map<String, Integer> tsCustomSize = new HashMap<>();
|
||||
private final Map<String, String> tsCustomLine = new HashMap<>();
|
||||
private final Map<String, ConfigStructure> structures = new HashMap<>();
|
||||
private final Map<String, ConfigStructureImpl> structures = new HashMap<>();
|
||||
private String headerMessage;
|
||||
// well, technically those should be a builder for state, not this state class itself
|
||||
private String tsFileOutputName = "rusefi.ini";
|
||||
|
@ -86,7 +86,7 @@ public class ReaderState implements IReaderState {
|
|||
ConfigField bitField = new ConfigField(state, bitNameParts[0], comment, null, BOOLEAN_T, new int[0], null, false, false, false, trueName, falseName);
|
||||
if (state.stack.isEmpty())
|
||||
throw new IllegalStateException("Parent structure expected");
|
||||
ConfigStructure structure = state.stack.peek();
|
||||
ConfigStructureImpl structure = state.stack.peek();
|
||||
structure.addBitField(bitField);
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ public class ReaderState implements IReaderState {
|
|||
private void handleEndStruct(List<ConfigurationConsumer> consumers) throws IOException {
|
||||
if (stack.isEmpty())
|
||||
throw new IllegalStateException("Unexpected end_struct");
|
||||
ConfigStructure structure = stack.pop();
|
||||
ConfigStructureImpl structure = stack.pop();
|
||||
if (log.debugEnabled())
|
||||
log.debug("Ending structure " + structure.getName());
|
||||
structure.addAlignmentFill(this, 4);
|
||||
|
@ -259,7 +259,7 @@ public class ReaderState implements IReaderState {
|
|||
}
|
||||
|
||||
private void addBitPadding() {
|
||||
ConfigStructure structure = stack.peek();
|
||||
ConfigStructureImpl structure = stack.peek();
|
||||
structure.addBitPadding(this);
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ public class ReaderState implements IReaderState {
|
|||
name = line;
|
||||
comment = null;
|
||||
}
|
||||
ConfigStructure structure = new ConfigStructure(name, comment, withPrefix);
|
||||
ConfigStructureImpl structure = new ConfigStructureImpl(name, comment, withPrefix);
|
||||
state.stack.push(structure);
|
||||
if (log.debugEnabled())
|
||||
log.debug("Starting structure " + structure.getName());
|
||||
|
@ -301,7 +301,7 @@ public class ReaderState implements IReaderState {
|
|||
|
||||
if (state.stack.isEmpty())
|
||||
throw new IllegalStateException(cf.getName() + ": Not enclosed in a struct");
|
||||
ConfigStructure structure = state.stack.peek();
|
||||
ConfigStructureImpl structure = state.stack.peek();
|
||||
|
||||
Integer getPrimitiveSize = TypesHelper.getPrimitiveSize(cf.getType());
|
||||
Integer customTypeSize = state.tsCustomSize.get(cf.getType());
|
||||
|
@ -390,7 +390,7 @@ public class ReaderState implements IReaderState {
|
|||
return tsCustomSize;
|
||||
}
|
||||
|
||||
public Map<String, ConfigStructure> getStructures() {
|
||||
public Map<String, ConfigStructureImpl> getStructures() {
|
||||
return structures;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,148 +1,19 @@
|
|||
package com.rusefi.output;
|
||||
|
||||
import com.rusefi.BitState;
|
||||
import com.rusefi.ConfigField;
|
||||
import com.rusefi.ReaderState;
|
||||
import com.rusefi.TypesHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.rusefi.ConfigField.BOOLEAN_T;
|
||||
public interface ConfigStructure {
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Mutable representation of a list of related {@link ConfigField}
|
||||
* <p>
|
||||
* Andrey Belomutskiy, (c) 2013-2020
|
||||
* 1/15/15
|
||||
*/
|
||||
public class ConfigStructure {
|
||||
public static final String ALIGNMENT_FILL_AT = "alignmentFill_at_";
|
||||
public static final String UNUSED_ANYTHING_PREFIX = "unused";
|
||||
public static final String UNUSED_BIT_PREFIX = "unusedBit_";
|
||||
int getTotalSize();
|
||||
|
||||
private final String name;
|
||||
private final String comment;
|
||||
private final boolean withPrefix;
|
||||
private final List<ConfigField> cFields = new ArrayList<>();
|
||||
private final List<ConfigField> tsFields = new ArrayList<>();
|
||||
List<ConfigField> getTsFields();
|
||||
|
||||
private int totalSize;
|
||||
List<ConfigField> getcFields();
|
||||
|
||||
private final BitState readingBitState = new BitState();
|
||||
boolean isWithPrefix();
|
||||
|
||||
private ConfigField cPrevField = ConfigField.VOID;
|
||||
private final Set<String> names = new HashSet<>();
|
||||
|
||||
public ConfigStructure(String name, String comment, boolean withPrefix) {
|
||||
this.name = name;
|
||||
this.comment = comment;
|
||||
this.withPrefix = withPrefix;
|
||||
}
|
||||
|
||||
public void addBitField(ConfigField bitField) {
|
||||
addBoth(bitField);
|
||||
this.readingBitState.incrementBitIndex(bitField);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void addAlignmentFill(ReaderState state, int alignment) {
|
||||
if (alignment == 0)
|
||||
return;
|
||||
/**
|
||||
* we make alignment decision based on C fields since we expect iteration and non-iteration fields
|
||||
* to match in size
|
||||
*/
|
||||
FieldIteratorWithOffset iterator = new FieldIteratorWithOffset(cFields) {
|
||||
@Override
|
||||
public void end() {
|
||||
super.end();
|
||||
currentOffset += cf.getSize(next);
|
||||
}
|
||||
};
|
||||
iterator.loop();
|
||||
|
||||
totalSize = iterator.currentOffset;
|
||||
int fillSize = totalSize % alignment == 0 ? 0 : alignment - (totalSize % alignment);
|
||||
if (fillSize > 3)
|
||||
throw new IllegalStateException("Fill size does not look right: " + fillSize);
|
||||
|
||||
if (fillSize != 0) {
|
||||
int[] fillSizeArray;
|
||||
if (fillSize != 1) {
|
||||
fillSizeArray = new int[1];
|
||||
fillSizeArray[0] = fillSize;
|
||||
} else {
|
||||
fillSizeArray = new int[0];
|
||||
}
|
||||
ConfigField fill = new ConfigField(state, ALIGNMENT_FILL_AT + totalSize, "need 4 byte alignment",
|
||||
"" + fillSize,
|
||||
TypesHelper.UINT8_T, fillSizeArray, "\"units\", 1, 0, -20, 100, 0", false, false, false, null, null);
|
||||
addBoth(fill);
|
||||
}
|
||||
totalSize += fillSize;
|
||||
}
|
||||
|
||||
public void addBoth(ConfigField cf) {
|
||||
addC(cf);
|
||||
tsFields.add(cf);
|
||||
}
|
||||
|
||||
public void addC(ConfigField cf) {
|
||||
// skip duplicate names - that's the weird use-case of conditional project definition like lambdaTable
|
||||
if (cf.getName().equals(cPrevField.getName()))
|
||||
return;
|
||||
|
||||
boolean isNew = names.add(cf.getName());
|
||||
if (!isNew)
|
||||
throw new IllegalStateException(cf.getName() + " name already used");
|
||||
|
||||
cFields.add(cf);
|
||||
|
||||
cPrevField = cf;
|
||||
}
|
||||
|
||||
public void addTs(ConfigField cf) {
|
||||
tsFields.add(cf);
|
||||
}
|
||||
|
||||
public void addBitPadding(ReaderState readerState) {
|
||||
if (readingBitState.get() == 0)
|
||||
return;
|
||||
int sizeAtStartOfPadding = cFields.size();
|
||||
while (readingBitState.get() < 32) {
|
||||
ConfigField bitField = new ConfigField(readerState, UNUSED_BIT_PREFIX + sizeAtStartOfPadding + "_" + readingBitState.get(), "", null, BOOLEAN_T, new int[0], null, false, false, false, null, null);
|
||||
addBitField(bitField);
|
||||
}
|
||||
readingBitState.reset();
|
||||
}
|
||||
|
||||
public int getTotalSize() {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public List<ConfigField> getTsFields() {
|
||||
return tsFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* We have two different collections because if 'array iterate' feature which is handled differently
|
||||
* in C and TS
|
||||
*/
|
||||
public List<ConfigField> getcFields() {
|
||||
return cFields;
|
||||
}
|
||||
|
||||
public boolean isWithPrefix() {
|
||||
return withPrefix;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
String getComment();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package com.rusefi.output;
|
||||
|
||||
import com.rusefi.BitState;
|
||||
import com.rusefi.ConfigField;
|
||||
import com.rusefi.ReaderState;
|
||||
import com.rusefi.TypesHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.rusefi.ConfigField.BOOLEAN_T;
|
||||
|
||||
/**
|
||||
* Mutable representation of a list of related {@link ConfigField}
|
||||
* <p>
|
||||
* Andrey Belomutskiy, (c) 2013-2020
|
||||
* 1/15/15
|
||||
*/
|
||||
public class ConfigStructureImpl implements ConfigStructure {
|
||||
public static final String ALIGNMENT_FILL_AT = "alignmentFill_at_";
|
||||
public static final String UNUSED_ANYTHING_PREFIX = "unused";
|
||||
public static final String UNUSED_BIT_PREFIX = "unusedBit_";
|
||||
|
||||
private final String name;
|
||||
private final String comment;
|
||||
private final boolean withPrefix;
|
||||
private final List<ConfigField> cFields = new ArrayList<>();
|
||||
private final List<ConfigField> tsFields = new ArrayList<>();
|
||||
|
||||
private int totalSize;
|
||||
|
||||
private final BitState readingBitState = new BitState();
|
||||
|
||||
private ConfigField cPrevField = ConfigField.VOID;
|
||||
private final Set<String> names = new HashSet<>();
|
||||
|
||||
public ConfigStructureImpl(String name, String comment, boolean withPrefix) {
|
||||
this.name = name;
|
||||
this.comment = comment;
|
||||
this.withPrefix = withPrefix;
|
||||
}
|
||||
|
||||
public void addBitField(ConfigField bitField) {
|
||||
addBoth(bitField);
|
||||
this.readingBitState.incrementBitIndex(bitField);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void addAlignmentFill(ReaderState state, int alignment) {
|
||||
if (alignment == 0)
|
||||
return;
|
||||
/**
|
||||
* we make alignment decision based on C fields since we expect iteration and non-iteration fields
|
||||
* to match in size
|
||||
*/
|
||||
FieldIteratorWithOffset iterator = new FieldIteratorWithOffset(cFields) {
|
||||
@Override
|
||||
public void end() {
|
||||
super.end();
|
||||
currentOffset += cf.getSize(next);
|
||||
}
|
||||
};
|
||||
iterator.loop();
|
||||
|
||||
totalSize = iterator.currentOffset;
|
||||
int fillSize = totalSize % alignment == 0 ? 0 : alignment - (totalSize % alignment);
|
||||
if (fillSize > 3)
|
||||
throw new IllegalStateException("Fill size does not look right: " + fillSize);
|
||||
|
||||
if (fillSize != 0) {
|
||||
int[] fillSizeArray;
|
||||
if (fillSize != 1) {
|
||||
fillSizeArray = new int[1];
|
||||
fillSizeArray[0] = fillSize;
|
||||
} else {
|
||||
fillSizeArray = new int[0];
|
||||
}
|
||||
ConfigField fill = new ConfigField(state, ALIGNMENT_FILL_AT + totalSize, "need 4 byte alignment",
|
||||
"" + fillSize,
|
||||
TypesHelper.UINT8_T, fillSizeArray, "\"units\", 1, 0, -20, 100, 0", false, false, false, null, null);
|
||||
addBoth(fill);
|
||||
}
|
||||
totalSize += fillSize;
|
||||
}
|
||||
|
||||
public void addBoth(ConfigField cf) {
|
||||
addC(cf);
|
||||
tsFields.add(cf);
|
||||
}
|
||||
|
||||
public void addC(ConfigField cf) {
|
||||
// skip duplicate names - that's the weird use-case of conditional project definition like lambdaTable
|
||||
if (cf.getName().equals(cPrevField.getName()))
|
||||
return;
|
||||
|
||||
boolean isNew = names.add(cf.getName());
|
||||
if (!isNew)
|
||||
throw new IllegalStateException(cf.getName() + " name already used");
|
||||
|
||||
cFields.add(cf);
|
||||
|
||||
cPrevField = cf;
|
||||
}
|
||||
|
||||
public void addTs(ConfigField cf) {
|
||||
tsFields.add(cf);
|
||||
}
|
||||
|
||||
public void addBitPadding(ReaderState readerState) {
|
||||
if (readingBitState.get() == 0)
|
||||
return;
|
||||
int sizeAtStartOfPadding = cFields.size();
|
||||
while (readingBitState.get() < 32) {
|
||||
ConfigField bitField = new ConfigField(readerState, UNUSED_BIT_PREFIX + sizeAtStartOfPadding + "_" + readingBitState.get(), "", null, BOOLEAN_T, new int[0], null, false, false, false, null, null);
|
||||
addBitField(bitField);
|
||||
}
|
||||
readingBitState.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalSize() {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ConfigField> getTsFields() {
|
||||
return tsFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* We have two different collections because if 'array iterate' feature which is handled differently
|
||||
* in C and TS
|
||||
*/
|
||||
@Override
|
||||
public List<ConfigField> getcFields() {
|
||||
return cFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWithPrefix() {
|
||||
return withPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ public class DataLogConsumer implements ConfigurationConsumer {
|
|||
// https://github.com/rusefi/web_backend/issues/166
|
||||
private static final int MSQ_LENGTH_LIMIT = 34;
|
||||
|
||||
public static final String UNUSED = ConfigStructure.UNUSED_ANYTHING_PREFIX;
|
||||
public static final String UNUSED = ConfigStructureImpl.UNUSED_ANYTHING_PREFIX;
|
||||
private final String fileName;
|
||||
private final StringBuilder tsWriter = new StringBuilder();
|
||||
private final TreeSet<String> comments = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
|
|
@ -38,16 +38,16 @@ public class FragmentDialogConsumer implements ConfigurationConsumer {
|
|||
int writeOneField(FieldIterator iterator, String prefix, int tsPosition) {
|
||||
ConfigField configField = iterator.cf;
|
||||
|
||||
if (configField.getName().startsWith(ConfigStructure.ALIGNMENT_FILL_AT))
|
||||
if (configField.getName().startsWith(ConfigStructureImpl.ALIGNMENT_FILL_AT))
|
||||
return 0;
|
||||
|
||||
ConfigStructure cs = configField.getStructureType();
|
||||
ConfigStructureImpl cs = configField.getStructureType();
|
||||
if (cs != null) {
|
||||
String extraPrefix = cs.isWithPrefix() ? configField.getName() + "_" : "";
|
||||
return writeFields(cs.getTsFields(), prefix + extraPrefix, tsPosition);
|
||||
}
|
||||
|
||||
if (configField.getName().startsWith(ConfigStructure.UNUSED_BIT_PREFIX))
|
||||
if (configField.getName().startsWith(ConfigStructureImpl.UNUSED_BIT_PREFIX))
|
||||
return 0;
|
||||
|
||||
if (configField.isBit()) {
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.rusefi.output.ConfigStructure.ALIGNMENT_FILL_AT;
|
||||
import static com.rusefi.output.ConfigStructureImpl.ALIGNMENT_FILL_AT;
|
||||
import static com.rusefi.output.DataLogConsumer.UNUSED;
|
||||
import static com.rusefi.output.GetOutputValueConsumer.getHashConflicts;
|
||||
import static com.rusefi.output.GetOutputValueConsumer.wrapSwitchStatement;
|
||||
|
|
|
@ -12,7 +12,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.rusefi.output.ConfigStructure.ALIGNMENT_FILL_AT;
|
||||
import static com.rusefi.output.ConfigStructureImpl.ALIGNMENT_FILL_AT;
|
||||
import static com.rusefi.output.DataLogConsumer.UNUSED;
|
||||
import static com.rusefi.output.GetConfigValueConsumer.FILE_HEADER;
|
||||
import static com.rusefi.output.GetConfigValueConsumer.getCompareName;
|
||||
|
|
|
@ -64,7 +64,7 @@ public abstract class JavaFieldsConsumer implements ConfigurationConsumer {
|
|||
if (configField.getName().equals(prev.getName())) {
|
||||
return tsPosition;
|
||||
}
|
||||
ConfigStructure cs = configField.getStructureType();
|
||||
ConfigStructureImpl cs = configField.getStructureType();
|
||||
if (cs != null) {
|
||||
String extraPrefix = cs.isWithPrefix() ? configField.getName() + "_" : "";
|
||||
return writeFields(cs.getTsFields(), prefix + extraPrefix, tsPosition);
|
||||
|
|
|
@ -30,7 +30,7 @@ class PerFieldWithStructuresIterator extends FieldIterator {
|
|||
|
||||
@Override
|
||||
public void end() {
|
||||
ConfigStructure cs = cf.getState().getStructures().get(cf.getType());
|
||||
ConfigStructureImpl cs = cf.getState().getStructures().get(cf.getType());
|
||||
String content;
|
||||
if (cs != null) {
|
||||
if (cf.isFromIterate()) {
|
||||
|
|
|
@ -23,9 +23,9 @@ public class SdCardFieldsContent {
|
|||
}
|
||||
|
||||
private String processOutput(ConfigField configField, String prefix) {
|
||||
if (configField.getName().startsWith(ConfigStructure.ALIGNMENT_FILL_AT))
|
||||
if (configField.getName().startsWith(ConfigStructureImpl.ALIGNMENT_FILL_AT))
|
||||
return "";
|
||||
if (configField.getName().startsWith(ConfigStructure.UNUSED_ANYTHING_PREFIX))
|
||||
if (configField.getName().startsWith(ConfigStructureImpl.UNUSED_ANYTHING_PREFIX))
|
||||
return "";
|
||||
if (configField.isBit())
|
||||
return "";
|
||||
|
|
|
@ -49,12 +49,12 @@ public class TsOutput {
|
|||
*/
|
||||
if (!usedNames.add(nameWithPrefix)
|
||||
&& !isConstantsSection
|
||||
&& !configField.getName().startsWith(ConfigStructure.ALIGNMENT_FILL_AT)
|
||||
&& !configField.getName().startsWith(ConfigStructure.UNUSED_ANYTHING_PREFIX)) {
|
||||
&& !configField.getName().startsWith(ConfigStructureImpl.ALIGNMENT_FILL_AT)
|
||||
&& !configField.getName().startsWith(ConfigStructureImpl.UNUSED_ANYTHING_PREFIX)) {
|
||||
throw new IllegalStateException(nameWithPrefix + " already present: " + configField);
|
||||
}
|
||||
|
||||
if (configField.getName().startsWith(ConfigStructure.ALIGNMENT_FILL_AT)) {
|
||||
if (configField.getName().startsWith(ConfigStructureImpl.ALIGNMENT_FILL_AT)) {
|
||||
tsPosition += configField.getSize(next);
|
||||
return tsPosition;
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ public class TsOutput {
|
|||
return tsPosition;
|
||||
}
|
||||
|
||||
ConfigStructure cs = configField.getStructureType();
|
||||
ConfigStructureImpl cs = configField.getStructureType();
|
||||
if (configField.getComment() != null && configField.getComment().trim().length() > 0 && cs == null) {
|
||||
String commentContent = configField.getCommentTemplated();
|
||||
commentContent = ConfigField.unquote(commentContent);
|
||||
|
@ -78,7 +78,7 @@ public class TsOutput {
|
|||
}
|
||||
|
||||
if (configField.isBit()) {
|
||||
if (!configField.getName().startsWith(ConfigStructure.UNUSED_BIT_PREFIX)) {
|
||||
if (!configField.getName().startsWith(ConfigStructureImpl.UNUSED_BIT_PREFIX)) {
|
||||
tsHeader.append(nameWithPrefix + " = bits, U32,");
|
||||
tsHeader.append(" " + tsPosition + ", [");
|
||||
tsHeader.append(bitIndex + ":" + bitIndex);
|
||||
|
|
|
@ -5,7 +5,7 @@ import com.rusefi.ReaderState;
|
|||
import com.rusefi.TypesHelper;
|
||||
import com.rusefi.VariableRegistry;
|
||||
import com.rusefi.output.BaseCHeaderConsumer;
|
||||
import com.rusefi.output.ConfigStructure;
|
||||
import com.rusefi.output.ConfigStructureImpl;
|
||||
import com.rusefi.output.JavaFieldsConsumer;
|
||||
import com.rusefi.output.TSProjectConsumer;
|
||||
import org.junit.Test;
|
||||
|
@ -163,7 +163,7 @@ public class ConfigFieldParserTest {
|
|||
|
||||
assertEquals(16, TypesHelper.getElementSize(state, "pid_s"));
|
||||
|
||||
ConfigStructure structure = state.getStructures().get("pid_s");
|
||||
ConfigStructureImpl structure = state.getStructures().get("pid_s");
|
||||
ConfigField firstField = structure.getcFields().get(0);
|
||||
assertEquals("ms", firstField.getUnits());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue