package org.goko.core.gcode.rs274ngcv3; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.SubMonitor; import org.goko.core.common.exception.GkException; import org.goko.core.common.exception.GkFunctionalException; import org.goko.core.common.exception.GkTechnicalException; import org.goko.core.common.measure.quantity.Time; import org.goko.core.common.service.AbstractGokoService; import org.goko.core.common.utils.CacheByCode; import org.goko.core.common.utils.CacheById; import org.goko.core.common.utils.SequentialIdGenerator; import org.goko.core.common.utils.UniqueCacheByCode; import org.goko.core.gcode.element.GCodeLine; import org.goko.core.gcode.element.GCodeWord; import org.goko.core.gcode.element.IGCodeProvider; import org.goko.core.gcode.element.IGCodeProviderSource; import org.goko.core.gcode.element.IGCodeProviderSourceListener; import org.goko.core.gcode.element.IInstructionProvider; import org.goko.core.gcode.rs274ngcv3.context.GCodeContext; import org.goko.core.gcode.rs274ngcv3.element.GCodeProvider; import org.goko.core.gcode.rs274ngcv3.element.IModifier; import org.goko.core.gcode.rs274ngcv3.element.IStackableGCodeProvider; import org.goko.core.gcode.rs274ngcv3.element.InstructionIterator; import org.goko.core.gcode.rs274ngcv3.element.InstructionProvider; import org.goko.core.gcode.rs274ngcv3.element.InstructionSet; import org.goko.core.gcode.rs274ngcv3.element.StackableGCodeProviderModifier; import org.goko.core.gcode.rs274ngcv3.element.StackableGCodeProviderRoot; import org.goko.core.gcode.rs274ngcv3.element.source.StringGCodeSource; import org.goko.core.gcode.rs274ngcv3.instruction.AbstractInstruction; import org.goko.core.gcode.rs274ngcv3.instruction.InstructionFactory; import org.goko.core.gcode.rs274ngcv3.instruction.executiontime.InstructionTimeCalculatorFactory; import org.goko.core.gcode.rs274ngcv3.modifier.AbstractModifier; import org.goko.core.gcode.rs274ngcv3.modifier.IModifierListener; import org.goko.core.gcode.rs274ngcv3.modifier.ModifierSorter; import org.goko.core.gcode.rs274ngcv3.modifier.ModifierSorter.EnumModifierSortType; import org.goko.core.gcode.rs274ngcv3.parser.GCodeLexer; import org.goko.core.gcode.rs274ngcv3.parser.GCodeToken; import org.goko.core.gcode.rs274ngcv3.parser.GCodeTokenType; import org.goko.core.gcode.rs274ngcv3.parser.ModalGroup; import org.goko.core.gcode.service.GCodeProviderDeleteEvent; import org.goko.core.gcode.service.IGCodeProviderDeleteVetoableListener; import org.goko.core.gcode.service.IGCodeProviderRepositoryListener; import org.goko.core.log.GkLog; import org.goko.core.math.BoundingTuple6b; import org.goko.core.math.Tuple6b; /** * @author Psyko */ public class RS274NGCServiceImpl extends AbstractGokoService implements IRS274NGCService{ private static final GkLog LOG = GkLog.getLogger(RS274NGCServiceImpl.class); /** The list of listener */ private List<IGCodeProviderRepositoryListener> listenerList; /** The list of modifier listener */ private List<IModifierListener> modifierListenerList; /** The list of modifier listener */ private List<IGCodeProviderDeleteVetoableListener> gcodeProviderDeleteListenerList; /** The list of modal groups */ private List<ModalGroup> modalGroups; /** The cache of root providers */ private CacheById<StackableGCodeProviderRoot> cacheRootProviders; /** The cache of providers */ private CacheById<IStackableGCodeProvider> cacheStackedProviders; /** The cache of providers */ private CacheByCode<IStackableGCodeProvider> cacheStackedProvidersByCode; /** The cache of modifiers */ private CacheById<IModifier<GCodeProvider>> cacheModifiers; /** Boolean allowing to enable/disable gcode update notification */ private boolean gcodeProviderUdateNotificationEnabled; /** The active rendering format */ private RenderingFormat renderingFormat; /** Constructor */ public RS274NGCServiceImpl() { initializeModalGroups(); this.listenerList = new CopyOnWriteArrayList<IGCodeProviderRepositoryListener>(); this.modifierListenerList = new CopyOnWriteArrayList<IModifierListener>(); this.cacheRootProviders = new CacheById<StackableGCodeProviderRoot>(new SequentialIdGenerator()); this.cacheStackedProviders = new CacheById<IStackableGCodeProvider>(); this.cacheStackedProvidersByCode = new UniqueCacheByCode<IStackableGCodeProvider>(); this.cacheModifiers = new CacheById<IModifier<GCodeProvider>>(new SequentialIdGenerator()); this.gcodeProviderUdateNotificationEnabled = true; this.gcodeProviderDeleteListenerList = new CopyOnWriteArrayList<IGCodeProviderDeleteVetoableListener>(); this.renderingFormat = RenderingFormat.COMPLETE; } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#getServiceId() */ @Override public String getServiceId() throws GkException { return "org.goko.core.gcode.rs274ngcv3.RS274NGCServiceImpl"; } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#start() */ @Override public void startService() throws GkException { LOG.info("Starting "+getServiceId()); LOG.info("Successfully started "+getServiceId()); } /** (inheritDoc) * @see org.goko.core.common.service.IGokoService#stop() */ @Override public void stopService() throws GkException { } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#reload(java.lang.Integer) */ @Override public void reload(Integer idGCodeProvider, IProgressMonitor monitor)throws GkException{ StackableGCodeProviderRoot root = cacheRootProviders.get(idGCodeProvider); // Create the new target provider GCodeProvider provider = new GCodeProvider(); provider.setId(root.getId()); provider.setSource(root.getSource()); provider.setCode(root.getCode()); parseProvider(provider.getSource(), provider, monitor); root.setParent(provider); root.setModificationDate(new Date()); // Force to update the modifiers stack cacheStackedProviders.get(idGCodeProvider).update(); notifyGCodeProviderUpdate(getGCodeProvider(idGCodeProvider)); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#parse(java.io.InputStream) */ @Override public IGCodeProvider parse(IGCodeProviderSource source, IProgressMonitor monitor) throws GkException { GCodeProvider provider = new GCodeProvider(); parseProvider(source, provider, monitor); return provider; } public void parseProvider(IGCodeProviderSource source, GCodeProvider provider, IProgressMonitor monitor) throws GkException { provider.clear(); provider.setSource(source); GCodeLexer lexer = new GCodeLexer(); InputStream stream = source.openInputStream(); List<List<GCodeToken>> tokens = lexer.tokenize(stream, provider); try { stream.close(); } catch (IOException e) { throw new GkTechnicalException(e); } if(!provider.hasErrors()){ SubMonitor subMonitor = null; if(monitor != null){ subMonitor = SubMonitor.convert(monitor,"Reading file", tokens.size()); } for (List<GCodeToken> lstToken : tokens) { verifyModality(lstToken); GCodeLine line = buildLine(lstToken); if(line != null){ provider.addLine(line); } if(subMonitor != null){ subMonitor.worked(1); } } if(subMonitor != null){ subMonitor.done(); } if(monitor != null){ subMonitor = SubMonitor.convert(monitor,"Veryfying file", 1); } getInstructions(new GCodeContext(), provider); if(subMonitor != null){ subMonitor.done(); } } } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#parse(java.lang.String) */ @Override public IGCodeProvider parse(String inputString) throws GkException { return parse(new StringGCodeSource(inputString), null); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#parseLine(java.lang.String) */ @Override public GCodeLine parseLine(String inputString) throws GkException { IGCodeProvider provider = parse(new StringGCodeSource(inputString), null); return provider.getLines().get(0); } /** * Build a GCodeLine using a list of tokens * @param lstToken the list of tokens to use * @return a GCodeLine * @throws GkException GkException */ private GCodeLine buildLine(List<GCodeToken> lstToken) throws GkException { GCodeLine line = new GCodeLine(); if(CollectionUtils.isNotEmpty(lstToken)){ for (GCodeToken token : lstToken) { // if(token.getType() == GCodeTokenType.LINE_NUMBER){ // line.setLineNumber(GCodeTokenUtils.getLineNumber(token)); // }else if(token.getType() == GCodeTokenType.WORD || token.getType() == GCodeTokenType.LINE_NUMBER){ line.addWord(new GCodeWord(StringUtils.substring(token.getValue(), 0, 1), StringUtils.substring(token.getValue(), 1))); }else if(token.getType() == GCodeTokenType.SIMPLE_COMMENT || token.getType() == GCodeTokenType.MULTILINE_COMMENT){ line.addWord(new GCodeWord(";", token.getValue())); }else if(token.getType() == GCodeTokenType.PERCENT){ line.addWord(new GCodeWord("%", StringUtils.EMPTY)); } } } return line; } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IGCodeService#getInstructions(org.goko.core.gcode.rs274ngcv3.context.GCodeContext, org.goko.core.gcode.element.IGCodeProvider) */ @Override public InstructionProvider getInstructions(final GCodeContext context, IGCodeProvider gcodeProvider) throws GkException { GCodeContext localContext = null; if(context != null){ localContext = new GCodeContext(context); } InstructionFactory factory = new InstructionFactory(); InstructionProvider instructionProvider = new InstructionProvider(); for (GCodeLine gCodeLine : gcodeProvider.getLines()) { InstructionSet iSet = new InstructionSet(); List<GCodeWord> localWords = new ArrayList<GCodeWord>(gCodeLine.getWords()); // A line can contain multiple instructions while(CollectionUtils.isNotEmpty(localWords)){ int wordCountBefore = localWords.size(); AbstractInstruction instruction = factory.build(localContext, localWords); if(instruction == null){ // We have words is the list, but we can't build any instruction from them. End while loop traceUnusedWords(gcodeProvider, localWords); break; }else{ // Make sure we consumed at least one word if(localWords.size() == wordCountBefore){ throw new GkTechnicalException("An instruction was created but no word was removed for provider ["+gcodeProvider.getCode()+"]. Instruction created : "+instruction.getClass()); } } instruction.setIdGCodeLine(gCodeLine.getId()); iSet.addInstruction(instruction); // Update context for further instructions update(localContext, instruction); } if(CollectionUtils.isNotEmpty(iSet.getInstructions())){ instructionProvider.addInstructionSet(iSet); } } return instructionProvider; } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#getGCodeProvider(org.goko.core.gcode.rs274ngcv3.context.GCodeContext, org.goko.core.gcode.rs274ngcv3.element.InstructionProvider) */ @Override public GCodeProvider getGCodeProvider(GCodeContext context, InstructionProvider instructionProvider) throws GkException { InstructionFactory factory = new InstructionFactory(); GCodeProvider provider = new GCodeProvider(); List<InstructionSet> sets = instructionProvider.getInstructionSets(); for (InstructionSet instructionSet : sets) { GCodeLine line = factory.getLine(context, instructionSet, renderingFormat); provider.addLine(line); } return provider; } /** * Trace the unused words in a line * @param unusedWords the list of unused words */ private void traceUnusedWords( IGCodeProvider gcodeProvider, List<GCodeWord> unusedWords){ String wordstr = ""; for (GCodeWord gCodeWord : unusedWords) { wordstr += gCodeWord.completeString() + " "; } LOG.warn("Provider ["+gcodeProvider.getCode()+"] - GCodeWord not supported "+wordstr+". They will be present in the GCode file, but won't generate instruction"); } /** * Verify modality for the given list of token * @param lstToken the list of tokens to check * @throws GkException GkException if there is a modality violation */ private void verifyModality(List<GCodeToken> lstToken) throws GkException{ for (ModalGroup group : modalGroups) { group.verifyModality(lstToken); } } /** * Initialize the list of modal groups */ protected void initializeModalGroups(){ this.modalGroups = new ArrayList<ModalGroup>(); this.modalGroups.add( new ModalGroup("G0", "G00", "G1", "G01", "G2", "G02", "G3", "G03", "G38.2", "G80", "G81", "G82", "G83", "G84", "G85", "G86", "G87", "G88", "G89" )); this.modalGroups.add( new ModalGroup("G17", "G18", "G19" )); this.modalGroups.add( new ModalGroup("G90", "G91")); this.modalGroups.add( new ModalGroup("G93", "G94")); this.modalGroups.add( new ModalGroup("G20", "G21")); this.modalGroups.add( new ModalGroup("G40", "G41", "G42")); this.modalGroups.add( new ModalGroup("G43","G49")); this.modalGroups.add( new ModalGroup("G98","G99")); this.modalGroups.add( new ModalGroup("G54", "G55", "G56", "G57", "G58", "G59", "G59.1", "G59.2", "G59.3")); this.modalGroups.add( new ModalGroup("G61", "G61.1", "G64")); this.modalGroups.add( new ModalGroup("M0", "M1", "M2", "M30", "M60")); this.modalGroups.add( new ModalGroup("M6")); this.modalGroups.add( new ModalGroup("M3", "M03","M4", "M04", "M5", "M05")); this.modalGroups.add( new ModalGroup("M7", "M07", "M9", "M09")); this.modalGroups.add( new ModalGroup("M8", "M08", "M9", "M09")); this.modalGroups.add( new ModalGroup("M48", "M49")); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#update(org.goko.core.gcode.element.IGCodeContext, org.goko.core.gcode.element.IInstruction) */ @Override public GCodeContext update(GCodeContext baseContext, AbstractInstruction instruction) throws GkException { instruction.apply(baseContext); return baseContext; }; /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#update(org.goko.core.gcode.element.IGCodeContext, org.goko.core.gcode.element.IInstructionSet) */ @Override public GCodeContext update(GCodeContext baseContext, InstructionSet instructionSet) throws GkException { GCodeContext result = baseContext; List<AbstractInstruction> instructions = instructionSet.getInstructions(); if(CollectionUtils.isNotEmpty(instructions)){ for (AbstractInstruction instruction : instructions) { result = update(result, instruction); } } return result; } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#update(org.goko.core.gcode.element.IGCodeContext, org.goko.core.gcode.element.IInstructionSet) */ @Override public GCodeContext update(GCodeContext baseContext, IInstructionProvider<AbstractInstruction, InstructionSet> instructionProvider) throws GkException { GCodeContext result = baseContext; List<InstructionSet> instructionSets = instructionProvider.getInstructionSets(); if(CollectionUtils.isNotEmpty(instructionSets)){ for (InstructionSet instructionSet : instructionSets) { result = update(result, instructionSet); } } return result; } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#getIterator(org.goko.core.gcode.element.IInstructionProvider, org.goko.core.gcode.element.IGCodeContext) */ @Override public InstructionIterator getIterator(IInstructionProvider<AbstractInstruction, InstructionSet> instructionProvider, GCodeContext baseContext) throws GkException { return new InstructionIterator(instructionProvider, new GCodeContext(baseContext), this); } /** (inheritDoc) * @see org.goko.core.execution.IGCodeExecutionTimeService#evaluateExecutionTime(org.goko.core.gcode.element.IGCodeProvider) */ @Override public Time evaluateExecutionTime(IGCodeProvider provider) throws GkException { Time result = Time.ZERO; InstructionTimeCalculatorFactory timeFactory = new InstructionTimeCalculatorFactory(); GCodeContext baseContext = new GCodeContext(); InstructionProvider instructions = getInstructions(baseContext, provider); InstructionIterator iterator = getIterator(instructions, baseContext); GCodeContext preContext = null; while(iterator.hasNext()){ preContext = new GCodeContext(iterator.getContext()); result = result.add( timeFactory.getExecutionTime(preContext, iterator.next()) ); } return result; } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#render(org.goko.core.gcode.element.GCodeLine) */ @Override public String render(GCodeLine line) throws GkException { return render(line, renderingFormat); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#render(org.goko.core.gcode.element.GCodeLine, org.goko.core.gcode.rs274ngcv3.RenderingFormat) */ @Override public String render(GCodeLine line, RenderingFormat customRenderingFormat) throws GkException { RenderingFormat localFormat = customRenderingFormat; if(localFormat == null){ localFormat = renderingFormat; } StringBuffer buffer = new StringBuffer(); // FIXME find a better way to classify GCode words or describe a GCodeLine within the rs274 service GCodeWord commentWord = null; // Add words for (GCodeWord word : line.getWords()) { if(StringUtils.equals(word.getLetter(), "N")){ if(!localFormat.isSkipLineNumbers()){ buffer.insert(0, word.getValue()); buffer.insert(0, word.getLetter()); } continue; } if(StringUtils.equals(word.getLetter(), ";")){ if(!localFormat.isSkipComments()){ commentWord = word; } continue; } if(buffer.length() > 0){ buffer.append(" "); } buffer.append(word.getLetter()); String value = word.getValue(); // Dirty temporary hack for decimal truncation if("XYZABCIJKSF".contains(word.getLetter())){ value = renderingFormat.format( new BigDecimal(value) ); } buffer.append(value); } // Add comment if(commentWord != null){ if(buffer.length() > 0){ buffer.append(" "); } buffer.append(commentWord.getValue()); } // utiliser un post processeur pour afficher le gcode ou voir pourquoi les decimales ne sont pas tronquées return buffer.toString(); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#getBounds(org.goko.core.gcode.rs274ngcv3.context.GCodeContext, org.goko.core.gcode.rs274ngcv3.element.InstructionProvider) */ @Override public BoundingTuple6b getBounds(GCodeContext context, InstructionProvider instructionProvider) throws GkException { Tuple6b min = new Tuple6b(); Tuple6b max = new Tuple6b(); GCodeContext preContext = new GCodeContext(context); InstructionIterator iterator = getIterator(instructionProvider, preContext); while (iterator.hasNext()) { preContext = new GCodeContext(iterator.getContext()); AbstractInstruction instruction = iterator.next(); Tuple6b endpoint = iterator.getContext().getPosition();//new Tuple6b(straightInstruction.getX(),straightInstruction.getY(),straightInstruction.getZ(),straightInstruction.getA(),straightInstruction.getB(),straightInstruction.getC()); min = min.min(endpoint); max = max.max(endpoint); // FIXME : add bound support for Arcs // if(instruction.getType() == InstructionType.STRAIGHT_TRAVERSE // || instruction.getType() == InstructionType.STRAIGHT_FEED){ // //AbstractStraightInstruction straightInstruction = (AbstractStraightInstruction) instruction; // Tuple6b endpoint = iterator.getContext().getPosition();//new Tuple6b(straightInstruction.getX(),straightInstruction.getY(),straightInstruction.getZ(),straightInstruction.getA(),straightInstruction.getB(),straightInstruction.getC()); // min = min.min(endpoint); // max = max.max(endpoint); // }else if(instruction.getType() == InstructionType.ARC_FEED){ // ArcFeedInstruction arcfeedInstruction = (ArcFeedInstruction) instruction; // // } } return new BoundingTuple6b(min, max); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#getGCodeProvider(java.lang.Integer) */ @Override public IGCodeProvider getGCodeProvider(final Integer id) throws GkException { // Make sure the required id exists internalGetGCodeProvider(id); return new RS274GCodeReference(this, id); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepositoryFriend#internalGetGCodeProvider(java.lang.Integer) */ @Override public IGCodeProvider internalGetGCodeProvider(Integer id) throws GkException { return cacheStackedProviders.get(id); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#findGCodeProvider(java.lang.Integer) */ @Override public IGCodeProvider findGCodeProvider(Integer id) throws GkException { return cacheStackedProviders.find(id); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#getGCodeProvider(java.lang.String) */ @Override public IGCodeProvider getGCodeProvider(String code) throws GkException { return cacheStackedProvidersByCode.get(code); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#getGCodeProvider() */ @Override public List<IGCodeProvider> getGCodeProvider() throws GkException { List<IGCodeProvider> result = new ArrayList<IGCodeProvider>(); List<IStackableGCodeProvider> lstProviders = cacheStackedProviders.get(); for (IGCodeProvider provider : lstProviders) { result.add(getGCodeProvider(provider.getId())); } return result; } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#addGCodeProvider(org.goko.core.gcode.element.IGCodeProvider) */ @Override public void addGCodeProvider(IGCodeProvider provider) throws GkException { LOG.info("Adding GCode provider code=["+provider.getCode()+"], id=["+provider.getId()+"]"); StackableGCodeProviderRoot wrappedProvider = new StackableGCodeProviderRoot(provider); cacheRootProviders.add(wrappedProvider); cacheStackedProviders.add(wrappedProvider); cacheStackedProvidersByCode.add(wrappedProvider); provider.setId(wrappedProvider.getId()); provider.getSource().bind(); provider.getSource().addListener(new GCodeSourceWatcher(provider.getId())); notifyGCodeProviderCreate(getGCodeProvider(wrappedProvider.getId())); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeService#deleteGCodeProvider(java.lang.Integer) */ @Override public void deleteGCodeProvider(Integer id) throws GkException { IGCodeProvider provider = cacheStackedProviders.get(id); boolean canDelete = checkDeleteGCodeProvider(id); if(canDelete){ LOG.info("Deleting GCode provider code=["+provider.getCode()+"], id=["+provider.getId()+"]"); IGCodeProvider notificationProvider = getGCodeProvider(provider.getId()); // Notifies before listeners first notifyBeforeGCodeProviderDelete(notificationProvider); // Remove attached modifiers performDeleteByIdGCodeProvider(id); // Update the provider once it's modified provider = cacheStackedProviders.get(id); cacheStackedProviders.remove(id); cacheStackedProvidersByCode.remove(provider.getCode()); provider.getSource().delete(); // Notifies after listeners notifyAfterGCodeProviderDelete(notificationProvider); } } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#lockGCodeProvider(java.lang.Integer) */ @Override public void lockGCodeProvider(Integer idGcodeProvider) throws GkException { IGCodeProvider provider = cacheStackedProviders.get(idGcodeProvider); if(!provider.isLocked()){ provider.setLocked(true); notifyGCodeProviderLocked(getGCodeProvider(provider.getId())); } } /** * Assert on the provider for the locked state * @param idGcodeProvider id of the provider to test * @throws GkException GkException */ void assertGCodeProviderUnlocked(Integer idGcodeProvider) throws GkException { if(isGCodeProviderUnlocked(idGcodeProvider)){ throw new GkFunctionalException("GCO-150"); } } /** * Determines if this provider is locked * @param idGcodeProvider id of the provider to test * @return <code>true</code> if it's locked, <code>false</code> otherwise * @throws GkException GkException */ boolean isGCodeProviderUnlocked(Integer idGcodeProvider) throws GkException { return cacheStackedProviders.get(idGcodeProvider).isLocked(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#unlockGCodeProvider(java.lang.Integer) */ @Override public void unlockGCodeProvider(Integer idGcodeProvider) throws GkException { IGCodeProvider provider = cacheStackedProviders.get(idGcodeProvider); if(provider.isLocked()){ provider.setLocked(false); notifyGCodeProviderUnlocked(getGCodeProvider(provider.getId())); } } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#addModifier(org.goko.core.gcode.rs274ngcv3.element.IModifier) */ @Override public void addModifier(IModifier<GCodeProvider> modifier) throws GkException { assertGCodeProviderUnlocked(modifier.getIdGCodeProvider()); // Assign the order of the modifier on the target GCodeProvider List<IModifier<GCodeProvider>> lstModifier = getModifierByGCodeProvider(modifier.getIdGCodeProvider()); modifier.setOrder(lstModifier.size()); this.cacheModifiers.add(modifier); if(modifier instanceof AbstractModifier){ // FIXME: found how to remove this dirty hack ((AbstractModifier) modifier).setRS274NGCService(this); } IStackableGCodeProvider baseProvider = this.cacheStackedProviders.get(modifier.getIdGCodeProvider()); StackableGCodeProviderModifier wrappedProvider = new StackableGCodeProviderModifier(baseProvider, modifier); this.cacheStackedProviders.remove(baseProvider.getId()); this.cacheStackedProviders.add(wrappedProvider); wrappedProvider.update(); notifyModifierCreate(modifier); notifyGCodeProviderUpdate(getGCodeProvider(wrappedProvider.getId())); } @Override public void setModifierOrder(IModifier<GCodeProvider> modifier, int order) throws GkException { assertGCodeProviderUnlocked(modifier.getIdGCodeProvider()); IStackableGCodeProvider stackableModifier = unchainModifier(modifier); // Let's find the current modifier at the given order IStackableGCodeProvider target = findStackableProviderForModifier(modifier.getIdGCodeProvider(), order); boolean chainBefore = true; if(target == null){ target = findStackableProviderForModifier(modifier.getIdGCodeProvider(), order - 1); chainBefore = false; } if(chainBefore){ chainModifierBefore(stackableModifier, target); }else{ chainModifierAfter(stackableModifier, target); } updateModifier(modifier); updateModifiersOrder(modifier.getIdGCodeProvider()); notifyGCodeProviderUpdate(getGCodeProvider(modifier.getIdGCodeProvider())); notifyModifierUpdate(modifier); } private void updateModifiersOrder(Integer idGCodeProvider) throws GkException{ assertGCodeProviderUnlocked(idGCodeProvider); IStackableGCodeProvider stackedProvider = cacheStackedProviders.get(idGCodeProvider); if(stackedProvider.getIdModifier() != null){ recursiveOrderUpdate( getModifier( stackedProvider.getIdModifier() ) ); } } private int recursiveOrderUpdate(IModifier<GCodeProvider> modifier) throws GkException{ IStackableGCodeProvider target = findStackableProviderForModifier(modifier.getId()); if(target.getParent() == null || target.getParent().getIdModifier() == null){ modifier.setOrder(0); return 0; } IModifier<GCodeProvider> parentModifier = getModifier( target.getParent().getIdModifier() ); int parentOrder = recursiveOrderUpdate(parentModifier); modifier.setOrder(parentOrder + 1); return modifier.getOrder(); } private void chainModifierAfter(IStackableGCodeProvider stackedModifier, IStackableGCodeProvider afterModifier) throws GkException{ IStackableGCodeProvider child = afterModifier.getChild(); afterModifier.setChild(stackedModifier); stackedModifier.setChild(child); stackedModifier.setParent(afterModifier); if(child != null){ child.setParent(stackedModifier); }else{ // There is no child. It means this was the latest modifier that was used as reference in the cacheStackedProviders cacheStackedProviders.remove(afterModifier); cacheStackedProviders.add(stackedModifier); } } private void chainModifierBefore(IStackableGCodeProvider modifier, IStackableGCodeProvider beforeModifier) throws GkException{ IStackableGCodeProvider parent = beforeModifier.getParent(); modifier.setChild(beforeModifier); beforeModifier.setParent(modifier); modifier.setParent(parent); if(parent != null){ parent.setChild(modifier); } } private IStackableGCodeProvider unchainModifier(IModifier<GCodeProvider> modifier) throws GkException{ assertGCodeProviderUnlocked(modifier.getIdGCodeProvider()); IStackableGCodeProvider stackedModifier = findStackableProviderForModifier(modifier.getId()); IStackableGCodeProvider parent = stackedModifier.getParent(); IStackableGCodeProvider child = stackedModifier.getChild(); if(parent != null){ parent.setChild(child); } if(child != null){ child.setParent(parent); }else{ // There is no child. It means this was the latest modifier that was used as reference in the cacheStackedProviders IGCodeProvider provider = getGCodeProvider(modifier.getIdGCodeProvider()); cacheStackedProviders.remove(provider.getId()); cacheStackedProviders.add(parent); } stackedModifier.setParent(null); stackedModifier.setChild(null); updateModifiersOrder(modifier.getIdGCodeProvider()); return stackedModifier; } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#updateModifier(org.goko.core.gcode.rs274ngcv3.element.IModifier) */ @Override public void updateModifier(IModifier<GCodeProvider> modifier) throws GkException { assertGCodeProviderUnlocked(modifier.getIdGCodeProvider()); // Make sure the modifier exists getModifier(modifier.getId()); this.cacheModifiers.remove(modifier.getId()); modifier.setModificationDate(new Date()); this.cacheModifiers.add(modifier); IStackableGCodeProvider provider = this.cacheStackedProviders.get(modifier.getIdGCodeProvider()); provider.update(); notifyModifierUpdate(modifier); notifyGCodeProviderUpdate(getGCodeProvider(provider.getId())); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#deleteModifier(org.goko.core.gcode.rs274ngcv3.element.IModifier) */ @Override public void deleteModifier(IModifier<GCodeProvider> modifier) throws GkException { deleteModifier(modifier.getId()); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#deleteModifier(org.goko.core.gcode.rs274ngcv3.element.IModifier) */ @Override public void deleteModifier(Integer idModifier) throws GkException { IModifier<GCodeProvider> modifier = this.cacheModifiers.get(idModifier); assertGCodeProviderUnlocked(modifier.getIdGCodeProvider()); // Remove the link of the modifier in the chain IStackableGCodeProvider stackableModifier = findStackableProviderForModifier(idModifier); IStackableGCodeProvider parent = stackableModifier.getParent(); IStackableGCodeProvider child = stackableModifier.getChild(); unchainModifier(modifier); // Force a refresh from the parent modifier IModifier<GCodeProvider> forceRefreshModifier = null; if(parent != null && parent.getIdModifier() != null){ forceRefreshModifier = getModifier(parent.getIdModifier()); }else if(child != null){ // Or child modifier forceRefreshModifier = getModifier(child.getIdModifier()); } if(forceRefreshModifier != null){ forceRefreshModifier.setModificationDate(new Date()); } // Definitely remove the modifier this.cacheModifiers.remove(idModifier); IStackableGCodeProvider targetProvider = cacheStackedProviders.get(modifier.getIdGCodeProvider()); //Force update targetProvider.update(); notifyModifierDelete(modifier); notifyGCodeProviderUpdate(getGCodeProvider(targetProvider.getId())); } /** * Returns the IStackableGCodeProvider for the given modifier * @param idModifier id of the modifier * @return IStackableGCodeProvider or <code>null</code> if none found * @throws GkException GkException */ private IStackableGCodeProvider findStackableProviderForModifier(Integer idModifier) throws GkException{ IModifier<GCodeProvider> modifier = getModifier(idModifier); // Let's start at the bottom most modifier on the gcode provider IStackableGCodeProvider currentModifier = cacheStackedProviders.get(modifier.getIdGCodeProvider()); while(currentModifier.getParent() != null){ if(ObjectUtils.equals(currentModifier.getIdModifier(), idModifier)){ // The id of the parent match the modifier we want the child for. We have our match return currentModifier; }else{ // We have a parent, but the id don't match. currentModifier = currentModifier.getParent(); } } return null; } /** * Returns the IStackableGCodeProvider for the given gcode provider and the given order * @param idGCodeProvider id of the gcode provider * * @return IStackableGCodeProvider or <code>null</code> if none found * @throws GkException GkException */ private IStackableGCodeProvider findStackableProviderForModifier(Integer idGCodeProvider, int order) throws GkException{ // Let's start at the bottom most modifier on the gcode provider IStackableGCodeProvider stackedProvider = cacheStackedProviders.get(idGCodeProvider); while(stackedProvider.getParent() != null){ if(stackedProvider.getIdModifier() != null){ IModifier<GCodeProvider> modifier = getModifier(stackedProvider.getIdModifier()); if(modifier.getOrder() == order){ return stackedProvider; } } // We have a parent, but the id don't match. stackedProvider = stackedProvider.getParent(); } return null; } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#getModifier(java.lang.Integer) */ @Override public IModifier<GCodeProvider> getModifier(Integer id) throws GkException { return cacheModifiers.get(id); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#findModifier(java.lang.Integer) */ @Override public IModifier<GCodeProvider> findModifier(Integer id) throws GkException { return cacheModifiers.find(id); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#getModifier(java.util.List) */ @Override public List<IModifier<GCodeProvider>> getModifier(List<Integer> lstId) throws GkException { return cacheModifiers.get(lstId); } @Override public List<IModifier<GCodeProvider>> getModifierByGCodeProvider(Integer idGcodeProvider) throws GkException { List<IModifier<GCodeProvider>> lstProviderModifiers = new ArrayList<IModifier<GCodeProvider>>(); List<IModifier<GCodeProvider>> lstModifiers = cacheModifiers.get(); if(CollectionUtils.isNotEmpty(lstModifiers)){ for (IModifier<GCodeProvider> iModifier : lstModifiers) { if(ObjectUtils.equals(iModifier.getIdGCodeProvider(), idGcodeProvider)){ lstProviderModifiers.add(iModifier); } } } Collections.sort(lstProviderModifiers, new ModifierSorter(EnumModifierSortType.ORDER)); return lstProviderModifiers; } // private IGCodeProvider applyModifiers(GCodeProvider provider) throws GkException { // List<IModifier<GCodeProvider>> lstModifiers = getModifierByGCodeProvider(provider.getId()); // GCodeProvider source = provider; // GCodeProvider target = null; // if(CollectionUtils.isNotEmpty(lstModifiers)){ // for (IModifier<GCodeProvider> modifier : lstModifiers) { // if(modifier.isEnabled()){ // target = new GCodeProvider(); // target.setId(source.getId()); // target.setCode(source.getCode()); // modifier.apply(source, target); // source = target; // } // } // } // return source; // } protected void performDeleteByIdGCodeProvider(Integer id) throws GkException{ List<IModifier<GCodeProvider>> lstModifiers = getModifierByGCodeProvider(id); if(CollectionUtils.isNotEmpty(lstModifiers)){ disableGcodeProviderUdateNotification(); for (IModifier<GCodeProvider> iModifier : lstModifiers) { deleteModifier(iModifier); } enableGcodeProviderUdateNotification(); } } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#addListener(org.goko.core.gcode.service.IGCodeProviderRepositoryListener) */ @Override public void addListener(IGCodeProviderRepositoryListener listener) throws GkException { listenerList.add( listener ); } /** (inheritDoc) * @see org.goko.core.gcode.rs274ngcv3.IRS274NGCService#addModifierListener(org.goko.core.gcode.rs274ngcv3.modifier.IModifierListener) */ @Override public void addModifierListener(IModifierListener listener){ modifierListenerList.add(listener); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#addDeleteVetoableListener(org.goko.core.gcode.service.IGCodeProviderDeleteVetoableListener) */ @Override public void addDeleteVetoableListener(IGCodeProviderDeleteVetoableListener listener) throws GkException { if(!gcodeProviderDeleteListenerList.contains(listener)){ gcodeProviderDeleteListenerList.add(listener); } } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#removeDeleteVetoableListener(org.goko.core.gcode.service.IGCodeProviderDeleteVetoableListener) */ @Override public void removeDeleteVetoableListener(IGCodeProviderDeleteVetoableListener listener) throws GkException { if(gcodeProviderDeleteListenerList.contains(listener)){ gcodeProviderDeleteListenerList.remove(listener); } } /** * Calls every delete listener to make sure the GCodeProvider can be deleted * @param idGCodeProvider the id of the provider to delete * @return <code>true</code> if it can be deleted, <code>false</code> otherwise */ protected boolean checkDeleteGCodeProvider(Integer idGCodeProvider){ GCodeProviderDeleteEvent event = new GCodeProviderDeleteEvent(idGCodeProvider); for (IGCodeProviderDeleteVetoableListener deleteListener : gcodeProviderDeleteListenerList) { deleteListener.beforeDelete(event); if(!event.isDoIt()){ break; } } return event.isDoIt(); } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#removeListener(org.goko.core.gcode.service.IGCodeProviderRepositoryListener) */ @Override public void removeListener(IGCodeProviderRepositoryListener listener) throws GkException { listenerList.remove( listener ); } /** * Notify the listener that the given GCodeProvider was updated * @param provider the target provider * @throws GkException GkException */ protected void notifyGCodeProviderUpdate(IGCodeProvider provider) throws GkException { if(isGcodeProviderUdateNotificationEnabled()){ if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.onGCodeProviderUpdate(provider); } } } } /** * Notify the listener that the given GCodeProvider was locked * @param provider the target provider * @throws GkException GkException */ protected void notifyGCodeProviderLocked(IGCodeProvider provider) throws GkException { if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.onGCodeProviderLocked(provider); } } } /** * Notify the listener that the given GCodeProvider was unlocked * @param provider the target provider * @throws GkException GkException */ protected void notifyGCodeProviderUnlocked(IGCodeProvider provider) throws GkException { if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.onGCodeProviderUnlocked(provider); } } } /** * Notify the listener that the given GCodeProvider was created * @param provider the target provider * @throws GkException GkException */ protected void notifyGCodeProviderCreate(IGCodeProvider provider) throws GkException { if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.onGCodeProviderCreate(provider); } } } /** * Notify the listener that the given GCodeProvider is about to be deleted * @param provider the target provider * @throws GkException GkException */ protected void notifyBeforeGCodeProviderDelete(IGCodeProvider provider) throws GkException { if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.beforeGCodeProviderDelete(provider); } } } /** * Notify the listener that the given GCodeProvider was deleted * @param provider the target provider * @throws GkException GkException */ protected void notifyAfterGCodeProviderDelete(IGCodeProvider provider) throws GkException { if (CollectionUtils.isNotEmpty(listenerList)) { for (IGCodeProviderRepositoryListener listener : listenerList) { listener.afterGCodeProviderDelete(provider); } } } /** * Notify the listener that the given modifier was created * @param modifier the target provider * @throws GkException GkException */ protected void notifyModifierCreate(IModifier<?> modifier) throws GkException { if (CollectionUtils.isNotEmpty(modifierListenerList)) { for (IModifierListener listener : modifierListenerList) { listener.onModifierCreate(modifier.getId()); } } } /** * Notify the listener that the given modifier was created * @param modifier the target provider * @throws GkException GkException */ protected void notifyModifierUpdate(IModifier<?> modifier) throws GkException { if (CollectionUtils.isNotEmpty(modifierListenerList)) { for (IModifierListener listener : modifierListenerList) { listener.onModifierUpdate(modifier.getId()); } } } /** * Notify the listener that the given modifier was created * @param modifier the target provider * @throws GkException GkException */ protected void notifyModifierDelete(IModifier<?> modifier) throws GkException { if (CollectionUtils.isNotEmpty(modifierListenerList)) { for (IModifierListener listener : modifierListenerList) { listener.onModifierDelete(modifier); } } } /** (inheritDoc) * @see org.goko.core.gcode.service.IGCodeProviderRepository#clearAll() */ @Override public void clearAll() throws GkException { for (IGCodeProvider gcodeProvider : getGCodeProvider()) { deleteGCodeProvider(gcodeProvider.getId()); } cacheModifiers.removeAll(); cacheStackedProviders.removeAll(); cacheStackedProvidersByCode.removeAll(); } /** * @return the gcodeProviderUdateNotificationEnabled */ protected boolean isGcodeProviderUdateNotificationEnabled() { return gcodeProviderUdateNotificationEnabled; } /** * @param gcodeProviderUdateNotificationEnabled the gcodeProviderUdateNotificationEnabled to set */ protected void setGcodeProviderUdateNotificationEnabled(boolean gcodeProviderUdateNotificationEnabled) { this.gcodeProviderUdateNotificationEnabled = gcodeProviderUdateNotificationEnabled; } protected void disableGcodeProviderUdateNotification(){ setGcodeProviderUdateNotificationEnabled(false); } protected void enableGcodeProviderUdateNotification(){ setGcodeProviderUdateNotificationEnabled(true); } /** * Listener class in charge of propagating source change events */ class GCodeSourceWatcher implements IGCodeProviderSourceListener{ /** The target provider */ private Integer idGCodeProvider; /** * @param idGCodeProvider the id of the target provider */ public GCodeSourceWatcher(Integer idGCodeProvider) { super(); this.idGCodeProvider = idGCodeProvider; } /** (inheritDoc) * @see org.goko.core.gcode.element.IGCodeProviderSourceListener#onSourceChanged(org.goko.core.gcode.element.IGCodeProviderSource) */ @Override public void onSourceChanged(IGCodeProviderSource taget) { try { RS274NGCServiceImpl.this.reload(idGCodeProvider, null); } catch (GkException e) { LOG.error(e); } } } /** * @return the renderingFormat */ public RenderingFormat getRenderingFormat() { return renderingFormat; } /** * @param renderingFormat the renderingFormat to set */ public void setRenderingFormat(RenderingFormat renderingFormat) { this.renderingFormat = renderingFormat; } }