blob: 7be44a8e6816fdc05d8d76d70676349d78c9c14b [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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 com.intellij.formatting;
import org.jetbrains.annotations.Nullable;
import java.util.*;
class WrapImpl extends Wrap {
/**
* The block where the wrap needs to happen if the CHOP wrap mode is used and the chain of blocks exceeds the right margin.
*/
private LeafBlockWrapper myChopStartBlock = null;
private int myWrapOffset = -1;
private int myFlags;
private static int ourId = 0;
private static final Set<WrapImpl> emptyParentsSet = Collections.emptySet();
private Set<WrapImpl> myParents = emptyParentsSet;
private Map<WrapImpl, Collection<LeafBlockWrapper>> myIgnoredWraps;
private static final int IGNORE_PARENT_WRAPS_MASK = 1;
private static final int ACTIVE_MASK = 2;
private static final int WRAP_FIRST_ELEMENT_MASK = 4;
private static final int TYPE_MASK = 0x18;
private static final int TYPE_SHIFT = 3;
private static final int ID_SHIFT = 5;
private static final int ID_MAX = 1 << 26;
private static final Type[] myTypes = Type.values();
public boolean isChildOf(@Nullable final WrapImpl wrap, LeafBlockWrapper leaf) {
if (getIgnoreParentWraps()) return false;
if (leaf != null && myIgnoredWraps != null) {
Collection<LeafBlockWrapper> leaves = myIgnoredWraps.get(wrap);
if (leaves != null && leaves.contains(leaf)) {
return false;
}
}
for (WrapImpl parent : myParents) {
if (parent == wrap) return true;
if (parent.isChildOf(wrap, leaf)) return true;
}
return false;
}
/**
* Allows to register given wrap as a parent of the current wrap.
* <p/>
* <code>'Parent'</code> wrap registration here means that {@link #isChildOf(WrapImpl, LeafBlockWrapper)} returns
* <code>'true'</code> if given wrap is used as a <code>'parent'</code> argument.
*
* @param parent parent wrap to register for the current wrap
*/
void registerParent(@Nullable WrapImpl parent) {
if (parent == this) return;
if (parent == null) return;
if (parent.isChildOf(this, null)) return;
if (myParents == emptyParentsSet) myParents = new HashSet<WrapImpl>(5);
myParents.add(parent);
}
/**
* Resets the following state of the current wrap object:
* <ul>
* <li>'{@link #getChopStartBlock() firstEntry}' property value is set to <code>null</code>;</li>
* <li>'{@link #getWrapOffset() firstPosition}' property value is set to <code>'-1'</code>;</li>
* <li>'{@link #isActive() isActive}' property value is set to <code>'false'</code>;</li>
* </ul>
*/
public void reset() {
myChopStartBlock = null;
myWrapOffset = -1;
myFlags &=~ ACTIVE_MASK;
}
/**
* Allows to check if single wrap is {@link #registerParent(WrapImpl) registered} for the current wrap and return
* it in case of success.
*
* @return single wrap registered as a parent of the current wrap if any;
* <code>null</code> if no wraps or more than one wrap is registered as a parent for the current wrap
*/
public WrapImpl getParent(){
if (myParents != null && myParents.size() == 1) {
return myParents.iterator().next();
}
return null;
}
public final boolean getIgnoreParentWraps() {
return (myFlags & IGNORE_PARENT_WRAPS_MASK) != 0;
}
/**
* Allows to mark given wrap as <code>'ignored'</code> for the given block. I.e. 'false' will be returned
* for subsequent calls to {@link #isChildOf(WrapImpl, LeafBlockWrapper)} with the same arguments.
*
* @param wrap target wrap
* @param currentBlock target block for which given wrap should be ignored
*/
public void ignoreParentWrap(@Nullable final WrapImpl wrap, final LeafBlockWrapper currentBlock) {
if (myIgnoredWraps == null) {
myIgnoredWraps = new HashMap<WrapImpl, Collection<LeafBlockWrapper>>(5);
}
if (myIgnoredWraps.get(wrap) == null) {
myIgnoredWraps.put(wrap, new HashSet<LeafBlockWrapper>(2));
}
myIgnoredWraps.get(wrap).add(currentBlock);
}
enum Type{
DO_NOT_WRAP, WRAP_AS_NEEDED, CHOP_IF_NEEDED, WRAP_ALWAYS
}
LeafBlockWrapper getChopStartBlock() {
return myChopStartBlock;
}
/**
* Performs the following changes at wrap object state:
* <ul>
* <li>'{@link #getChopStartBlock() firstEntry}' property value is dropped (set to <code>null</code>)</li>
* <li>'{@link #isActive() isActive}' property value is set (to <code>true</code>)</li>
* </ul>
*/
void setActive() {
myChopStartBlock = null;
myFlags |= ACTIVE_MASK;
}
/**
* Applies given value to the '{@link #getWrapOffset() firstPosition}' property value if it's value is undefined at the moment
* (has negative value).
*
* @param startOffset new '{@link #getWrapOffset() firstPosition}' property value to use if current value is undefined (negative)
*/
void setWrapOffset(final int startOffset) {
if (myWrapOffset < 0) {
myWrapOffset = startOffset;
}
}
/**
* @return '{@link #getWrapOffset() firstPosition}' property value defined previously via {@link #setWrapOffset(int)} if any;
* <code>'-1'</code> otherwise
*/
int getWrapOffset() {
return myWrapOffset;
}
public WrapImpl(WrapType type, boolean wrapFirstElement) {
Type myType;
switch(type) {
case NORMAL: myType = Type.WRAP_AS_NEEDED;break;
case NONE: myType= Type.DO_NOT_WRAP;break;
case ALWAYS: myType = Type.WRAP_ALWAYS; break;
case CHOP_DOWN_IF_LONG:
default: myType = Type.CHOP_IF_NEEDED;
}
int myId = ourId++;
assert myId < ID_MAX;
myFlags |= (wrapFirstElement ? WRAP_FIRST_ELEMENT_MASK:0) | (myType.ordinal() << TYPE_SHIFT) | (myId << ID_SHIFT);
}
final Type getType() {
return myTypes[(myFlags & TYPE_MASK) >>> TYPE_SHIFT];
}
/**
* Allows to check if current wrap object is configured to wrap first element. This property is defined at
* {@link #WrapImpl(WrapType, boolean) constructor} during object initialization and can't be changed later.
*
* @return <code>'wrapFirstElement'</code> property value
*/
final boolean isWrapFirstElement() {
return (myFlags & WRAP_FIRST_ELEMENT_MASK) != 0;
}
void saveChopBlock(LeafBlockWrapper current) {
if (myChopStartBlock == null) {
myChopStartBlock = current;
}
}
final boolean isActive() {
return (myFlags & ACTIVE_MASK) != 0;
}
public String toString() {
return getType().toString();
}
public String getId() {
return String.valueOf(myFlags >>> ID_SHIFT);
}
/**
* Allows to instruct current wrap to ignore all parent wraps, i.e. all calls to {@link #isChildOf(WrapImpl, LeafBlockWrapper)}
* return <code>'false'</code> after invocation of this method.
*/
@Override
public void ignoreParentWraps() {
myFlags |= IGNORE_PARENT_WRAPS_MASK;
}
}