001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang.ir;
012
013import java.util.Objects;
014import java.util.LinkedList;
015import java.util.List;
016
017/**
018 * A {@code try catch finally} statement.
019 *
020 * <p>Represents nodes such as:
021 * <pre class="listing"><code class="lang-golo" data-lang="golo">
022 * try {
023 *   # try block
024 * } catch (ex) {
025 *   # catch block
026 * } finally {
027 *   # finally block
028 * }
029 * </code></pre>
030 */
031public final class TryCatchFinally extends GoloStatement<TryCatchFinally> {
032
033  public static final String DUMMY_TRY_RESULT_VARIABLE = "__$$_result";
034  private static final String DUMMY_EXCEPTION_VARIABLE = "__$$_exception";
035  private String exceptionId;
036  private LocalReference dummyException;
037  private Block tryBlock;
038  private Block catchBlock;
039  private Block finallyBlock;
040
041  private TryCatchFinally() {
042    super();
043    this.exceptionId = null;
044  }
045
046  /**
047   * Creates an empty {@code try catch finally} statement.
048   */
049  public static TryCatchFinally tryCatch() {
050    return new TryCatchFinally();
051  }
052
053
054  /**
055   * Creates a complete {@code try catch finally} statement.
056   * <p>
057   * Less readable than the fluent API, but useful for meta-generation.
058   */
059  public static TryCatchFinally create(String exceptionName, GoloElement<?> tryBlock, GoloElement<?> catchBlock, GoloElement<?> finallyBlock) {
060    return new TryCatchFinally()
061      .trying(tryBlock)
062      .catching(exceptionName, catchBlock)
063      .finalizing(finallyBlock);
064  }
065
066  protected TryCatchFinally self() { return this; }
067
068  /**
069   * Get the exception name.
070   *
071   * <p>{@code ex} in the previous example.
072   */
073  public String getExceptionId() {
074    return exceptionId;
075  }
076
077  /**
078   * Returns the internal index of the exception reference.
079   */
080  public int getExceptionRefIndex() {
081    if (!this.hasCatchBlock()) {
082      return this.dummyException.getIndex();
083    }
084    return this.catchBlock.getReferenceTable().get(this.exceptionId).getIndex();
085  }
086
087  public Block getTryBlock() {
088    return tryBlock;
089  }
090
091  /**
092   * Defines the try block.
093   *
094   * <p>This is a builder method.
095   *
096   * @param block a {@link Block} or a statement that will be wrapped.
097   * @see Block#of(Object)
098   */
099  public TryCatchFinally trying(Object block) {
100    this.tryBlock = makeParentOf(Block.of(block));
101    return this;
102  }
103
104  public Block getCatchBlock() {
105    return catchBlock != null ? catchBlock : Block.nullBlock();
106  }
107
108  /**
109   * Defines the catch block.
110   *
111   * <p>This is a builder method.
112   *
113   * @param exceptionId the name of the catched exception instance.
114   * @param block a {@link Block} or a statement that will be wrapped.
115   * @see Block#of(Object)
116   */
117  public TryCatchFinally catching(String exceptionId, Object block) {
118    this.exceptionId = exceptionId;
119    return catching(block);
120  }
121
122  public TryCatchFinally catching(Object block) {
123    if (block == null) {
124      this.catchBlock = null;
125      this.exceptionId = null;
126      return this;
127    }
128    this.catchBlock = makeParentOf(Block.of(block));
129    this.catchBlock.getReferenceTable().add(LocalReference.of(exceptionId).synthetic());
130    return this;
131  }
132
133  public Block getFinallyBlock() {
134    return finallyBlock != null ? finallyBlock : Block.nullBlock();
135  }
136
137  /**
138   * Defines the finally block.
139   *
140   * <p>This is a builder method.
141   *
142   * @param block a {@link Block} or a statement that will be wrapped.
143   * @see Block#of(Object)
144   */
145  public TryCatchFinally finalizing(Object block) {
146    this.dummyException = LocalReference.generate(DUMMY_EXCEPTION_VARIABLE).synthetic();
147    this.finallyBlock = makeParentOf(Block.of(block));
148    this.finallyBlock.getReferenceTable().add(this.dummyException);
149    return this;
150  }
151
152  public String dummyExceptionName() {
153    return this.dummyException.getName();
154  }
155
156  public boolean hasFinallyBlock() {
157    return finallyBlock != null;
158  }
159
160  public boolean hasCatchBlock() {
161    return catchBlock != null;
162  }
163
164  /**
165   * Checks if a return is present in the try or catch block and a finally is present.
166   * <p>
167   * In this case we must not generate a return but a jump to the finally block.
168   */
169  public boolean mustJumpToFinally() {
170    return (this.tryBlock.hasReturn()
171          || (this.catchBlock != null && this.catchBlock.hasReturn()))
172      && this.finallyBlock != null;
173
174  }
175
176  public boolean hasReturn() {
177    return (this.tryBlock.hasReturn()
178            && (this.catchBlock == null || this.catchBlock.hasReturn()))
179        || (this.finallyBlock != null
180            && this.finallyBlock.hasReturn());
181  }
182
183  /**
184   * Check if this statement has both a catch block and a finally block.
185   */
186  public boolean isTryCatchFinally() {
187    return hasCatchBlock() && hasFinallyBlock();
188  }
189
190  /**
191   * Check if this statement has only a catch block.
192   */
193  public boolean isTryCatch() {
194    return hasCatchBlock() && !hasFinallyBlock();
195  }
196
197  /**
198   * Check if this statement has only a finally block.
199   */
200  public boolean isTryFinally() {
201    return !hasCatchBlock() && hasFinallyBlock();
202  }
203
204  /**
205   * {@inheritDoc}
206   */
207  @Override
208  public void accept(GoloIrVisitor visitor) {
209    visitor.visitTryCatchFinally(this);
210  }
211
212  /**
213   * {@inheritDoc}
214   */
215  @Override
216  public List<GoloElement<?>> children() {
217    LinkedList<GoloElement<?>> children = new LinkedList<>();
218    children.add(tryBlock);
219    if (catchBlock != null) {
220      children.add(catchBlock);
221    }
222    if (finallyBlock != null) {
223      children.add(finallyBlock);
224    }
225    return children;
226  }
227
228  /**
229   * {@inheritDoc}
230   */
231  @Override
232  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
233    if (Objects.equals(original, tryBlock)) {
234      trying(newElement);
235    } else if (Objects.equals(original, catchBlock)) {
236      catching(newElement);
237    } else if (Objects.equals(original, finallyBlock)) {
238      finalizing(newElement);
239    } else {
240      throw cantReplace(original, newElement);
241    }
242  }
243}