mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
723 lines
23 KiB
C
723 lines
23 KiB
C
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
|
|
//==============================================================================
|
|
struct ClassDatabase
|
|
{
|
|
//==============================================================================
|
|
struct MemberInfo
|
|
{
|
|
enum CodeLocationType
|
|
{
|
|
declaration = 0,
|
|
addedToParent,
|
|
setBoundsParamX,
|
|
setBoundsParamY,
|
|
setBoundsParamW,
|
|
setBoundsParamH,
|
|
|
|
// WARNING! When you change any of these, also update the copy that lives in the live editing code
|
|
|
|
numCodeLocationTypes
|
|
};
|
|
|
|
MemberInfo() {}
|
|
|
|
MemberInfo (const MemberInfo& other)
|
|
: name (other.name), type (other.type)
|
|
{
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
locations[i] = other.locations[i];
|
|
}
|
|
|
|
MemberInfo (const String& nm, const String& ty)
|
|
: name (nm), type (ty)
|
|
{
|
|
}
|
|
|
|
MemberInfo (const ValueTree& v)
|
|
: name (v [Ids::name].toString()),
|
|
type (v [Ids::class_].toString())
|
|
{
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
locations[i] = v [getIdentifierForCodeLocationType (i)].toString();
|
|
}
|
|
|
|
const String& getName() const { return name; }
|
|
const String& getType() const { return type; }
|
|
|
|
const SourceCodeRange& getLocation (CodeLocationType t) const
|
|
{
|
|
return locations[t];
|
|
}
|
|
|
|
void setLocation (CodeLocationType t, const SourceCodeRange& range)
|
|
{
|
|
locations[t] = range;
|
|
}
|
|
|
|
void mergeWith (const MemberInfo& other)
|
|
{
|
|
jassert (name == other.name);
|
|
if (other.type.isNotEmpty())
|
|
type = other.type;
|
|
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
if (other.locations[i].isValid())
|
|
locations[i] = other.locations[i];
|
|
}
|
|
|
|
void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta)
|
|
{
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
locations[i].nudge (file, insertPoint, delta);
|
|
}
|
|
|
|
void fileContentChanged (const String& file)
|
|
{
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
locations[i].fileContentChanged (file);
|
|
}
|
|
|
|
ValueTree toValueTree() const
|
|
{
|
|
ValueTree m (Ids::MEMBER);
|
|
m.setProperty (Ids::name, name, nullptr);
|
|
m.setProperty (Ids::class_, type, nullptr);
|
|
|
|
for (int i = 0; i < numCodeLocationTypes; ++i)
|
|
locations[i].writeToValueTree (m, getIdentifierForCodeLocationType (i));
|
|
|
|
return m;
|
|
}
|
|
|
|
private:
|
|
String name, type;
|
|
SourceCodeRange locations [numCodeLocationTypes];
|
|
|
|
static Identifier getIdentifierForCodeLocationType (int typeIndex)
|
|
{
|
|
// (These need to remain in order)
|
|
static_assert (setBoundsParamX + 1 == setBoundsParamY && setBoundsParamY + 1 == setBoundsParamW
|
|
&& setBoundsParamW + 1 == setBoundsParamH, "");
|
|
|
|
static const Identifier ids[] =
|
|
{
|
|
"declaration",
|
|
"addedToParent",
|
|
"setBoundsParamX",
|
|
"setBoundsParamY",
|
|
"setBoundsParamW",
|
|
"setBoundsParamH"
|
|
};
|
|
|
|
return ids [typeIndex];
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
struct MethodInfo
|
|
{
|
|
MethodInfo() {}
|
|
|
|
MethodInfo (const MethodInfo& other)
|
|
: name (other.name), returnType (other.returnType),
|
|
declaration (other.declaration), definition (other.definition),
|
|
numArgs (other.numArgs), flags (other.flags)
|
|
{
|
|
}
|
|
|
|
String name, returnType;
|
|
SourceCodeRange declaration, definition;
|
|
int numArgs, flags;
|
|
|
|
enum
|
|
{
|
|
isConstructor = 1,
|
|
isDefaultConstructor = 2,
|
|
isTemplated = 4,
|
|
isPublic = 8
|
|
};
|
|
|
|
MethodInfo (const ValueTree& v)
|
|
: name (v[Ids::name].toString()),
|
|
returnType (v[Ids::returnType].toString()),
|
|
declaration (v[Ids::declaration].toString()),
|
|
definition (v[Ids::definition].toString()),
|
|
numArgs (v[Ids::numArgs]),
|
|
flags (v[Ids::flags])
|
|
{
|
|
}
|
|
|
|
ValueTree toValueTree() const
|
|
{
|
|
ValueTree m (Ids::METHOD);
|
|
m.setProperty (Ids::name, name, nullptr);
|
|
m.setProperty (Ids::returnType, returnType, nullptr);
|
|
m.setProperty (Ids::numArgs, numArgs, nullptr);
|
|
m.setProperty (Ids::flags, flags, nullptr);
|
|
declaration.writeToValueTree (m, Ids::declaration);
|
|
definition.writeToValueTree (m, Ids::definition);
|
|
return m;
|
|
}
|
|
|
|
void nudgeAllCodeRanges (const String& file, const int insertPoint, const int delta)
|
|
{
|
|
declaration.nudge (file, insertPoint, delta);
|
|
definition.nudge (file, insertPoint, delta);
|
|
}
|
|
|
|
void fileContentChanged (const String& file)
|
|
{
|
|
declaration.fileContentChanged (file);
|
|
definition.fileContentChanged (file);
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
struct InstantiationFlags
|
|
{
|
|
InstantiationFlags()
|
|
: isAbstract (false),
|
|
inAnonymousNamespace (false),
|
|
noDefaultConstructor (false)
|
|
{}
|
|
|
|
InstantiationFlags (const InstantiationFlags& other)
|
|
: isAbstract (other.isAbstract),
|
|
inAnonymousNamespace (other.inAnonymousNamespace),
|
|
noDefaultConstructor (other.noDefaultConstructor)
|
|
{}
|
|
|
|
bool canBeInstantiated() const noexcept
|
|
{
|
|
return ! (isAbstract || inAnonymousNamespace || noDefaultConstructor);
|
|
}
|
|
|
|
String getReasonForUnavailability() const
|
|
{
|
|
if (isAbstract) return "This class is abstract";
|
|
if (noDefaultConstructor) return "This class has no default constructor";
|
|
if (inAnonymousNamespace) return "This class is declared inside an anonymous namespace";
|
|
return String();
|
|
}
|
|
|
|
bool isDisallowed (const InstantiationFlags& disallowedFlags) const
|
|
{
|
|
return ! ((disallowedFlags.isAbstract && isAbstract)
|
|
|| (disallowedFlags.inAnonymousNamespace && inAnonymousNamespace)
|
|
|| (disallowedFlags.noDefaultConstructor && noDefaultConstructor));
|
|
}
|
|
|
|
bool isAbstract;
|
|
bool inAnonymousNamespace;
|
|
bool noDefaultConstructor;
|
|
};
|
|
|
|
//==============================================================================
|
|
struct Class
|
|
{
|
|
Class() {}
|
|
~Class() {}
|
|
|
|
Class (const Class& other)
|
|
: className (other.className), members (other.members),
|
|
methods (other.methods), classDeclaration (other.classDeclaration),
|
|
instantiationFlags (other.instantiationFlags)
|
|
{
|
|
}
|
|
|
|
Class (const String& name, const InstantiationFlags& flags,
|
|
const Array<MemberInfo>& m,
|
|
const Array<MethodInfo>& meth,
|
|
const SourceCodeRange& classDeclarationRange)
|
|
: className (name),
|
|
members (m), methods (meth),
|
|
classDeclaration (classDeclarationRange),
|
|
instantiationFlags (flags)
|
|
{
|
|
}
|
|
|
|
Class& operator= (const Class& other)
|
|
{
|
|
className = other.className;
|
|
members = other.members;
|
|
methods = other.methods;
|
|
classDeclaration = other.classDeclaration;
|
|
instantiationFlags = other.instantiationFlags;
|
|
return *this;
|
|
}
|
|
|
|
const String& getName() const noexcept { return className; }
|
|
|
|
const InstantiationFlags& getInstantiationFlags() const
|
|
{
|
|
return instantiationFlags;
|
|
}
|
|
|
|
void setInstantiationFlags (const InstantiationFlags& newFlags)
|
|
{
|
|
instantiationFlags = newFlags;
|
|
}
|
|
|
|
const SourceCodeRange& getClassDeclarationRange() const
|
|
{
|
|
return classDeclaration;
|
|
}
|
|
|
|
const MemberInfo* findMember (const String& memberName) const
|
|
{
|
|
for (auto& m : members)
|
|
if (m.getName() == memberName)
|
|
return &m;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
MemberInfo* findMember (const String& memberName)
|
|
{
|
|
return const_cast<MemberInfo*> (static_cast<const Class&>(*this).findMember (memberName));
|
|
}
|
|
|
|
const MethodInfo* getDefaultConstructor() const
|
|
{
|
|
for (const MethodInfo& m : methods)
|
|
if ((m.flags & MethodInfo::isDefaultConstructor) != 0)
|
|
return &m;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const MethodInfo* getConstructor() const
|
|
{
|
|
if (const MethodInfo* m = getDefaultConstructor())
|
|
return m;
|
|
|
|
for (const MethodInfo& m : methods)
|
|
if ((m.flags & MethodInfo::isConstructor) != 0)
|
|
return &m;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const MethodInfo* getResizedMethod() const
|
|
{
|
|
for (const MethodInfo& m : methods)
|
|
if (m.name == "resized" && m.numArgs == 0)
|
|
return &m;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
File getMainSourceFile() const
|
|
{
|
|
if (const MethodInfo* m = getResizedMethod())
|
|
if (m->definition.isValid())
|
|
return m->definition.file;
|
|
|
|
if (const MethodInfo* m = getConstructor())
|
|
if (m->definition.isValid())
|
|
return m->definition.file;
|
|
|
|
for (auto& m : methods)
|
|
if (m.definition.isValid() && File (m.definition.file).hasFileExtension ("cpp;mm"))
|
|
return m.definition.file;
|
|
|
|
for (auto& m : methods)
|
|
if ((m.flags & MethodInfo::isConstructor) != 0 && m.definition.isValid())
|
|
return m.definition.file;
|
|
|
|
for (auto& m : methods)
|
|
if (m.definition.isValid() && File (m.definition.file).exists())
|
|
return m.definition.file;
|
|
|
|
return {};
|
|
}
|
|
|
|
Array<File> getAllSourceFiles() const
|
|
{
|
|
Array<File> files;
|
|
|
|
for (const MethodInfo& m : methods)
|
|
{
|
|
files.addIfNotAlreadyThere (m.declaration.file);
|
|
files.addIfNotAlreadyThere (m.definition.file);
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
bool isDeclaredInFile (const File& file) const
|
|
{
|
|
return file == classDeclaration.file;
|
|
}
|
|
|
|
void mergeWith (const Class& other)
|
|
{
|
|
jassert (*this == other);
|
|
|
|
if (other.classDeclaration.isValid())
|
|
classDeclaration = other.classDeclaration;
|
|
|
|
for (auto& m : other.members)
|
|
{
|
|
if (auto* existing = findMember (m.getName()))
|
|
existing->mergeWith (m);
|
|
else
|
|
members.add (m);
|
|
}
|
|
}
|
|
|
|
void nudgeAllCodeRanges (const String& file, int index, int delta)
|
|
{
|
|
for (MemberInfo& m : members) m.nudgeAllCodeRanges (file, index, delta);
|
|
for (MethodInfo& m : methods) m.nudgeAllCodeRanges (file, index, delta);
|
|
|
|
classDeclaration.nudge (file, index, delta);
|
|
}
|
|
|
|
void fileContentChanged (const String& file)
|
|
{
|
|
for (MemberInfo& m : members) m.fileContentChanged (file);
|
|
for (MethodInfo& m : methods) m.fileContentChanged (file);
|
|
|
|
classDeclaration.fileContentChanged (file);
|
|
}
|
|
|
|
Class (const ValueTree& v)
|
|
{
|
|
className = v[Ids::name];
|
|
instantiationFlags.isAbstract = v[Ids::abstract];
|
|
instantiationFlags.inAnonymousNamespace = v[Ids::anonymous];
|
|
instantiationFlags.noDefaultConstructor = v[Ids::noDefConstructor];
|
|
|
|
classDeclaration = v [Ids::classDecl].toString();
|
|
|
|
for (int i = 0; i < v.getNumChildren(); ++i)
|
|
members.add (MemberInfo (v.getChild(i)));
|
|
}
|
|
|
|
ValueTree toValueTree() const
|
|
{
|
|
ValueTree v (Ids::CLASS);
|
|
v.setProperty (Ids::name, className, nullptr);
|
|
v.setProperty (Ids::abstract, instantiationFlags.isAbstract, nullptr);
|
|
v.setProperty (Ids::anonymous, instantiationFlags.inAnonymousNamespace, nullptr);
|
|
v.setProperty (Ids::noDefConstructor, instantiationFlags.noDefaultConstructor, nullptr);
|
|
classDeclaration.writeToValueTree (v, Ids::classDecl);
|
|
|
|
for (const MemberInfo& m : members)
|
|
v.appendChild (m.toValueTree(), nullptr);
|
|
|
|
return v;
|
|
}
|
|
|
|
bool operator== (const Class& other) const noexcept { return className == other.className; }
|
|
bool operator!= (const Class& other) const noexcept { return ! operator== (other); }
|
|
bool operator< (const Class& other) const noexcept { return className < other.className; }
|
|
|
|
const Array<MemberInfo>& getMembers() const { return members; }
|
|
|
|
private:
|
|
String className;
|
|
Array<MemberInfo> members;
|
|
Array<MethodInfo> methods;
|
|
SourceCodeRange classDeclaration;
|
|
InstantiationFlags instantiationFlags;
|
|
|
|
JUCE_LEAK_DETECTOR (Class)
|
|
};
|
|
|
|
//==============================================================================
|
|
struct Namespace
|
|
{
|
|
Namespace() : name ("Global Namespace") {}
|
|
Namespace (const String& n, const String& full) : name (n), fullName (full) {}
|
|
|
|
bool isEmpty() const noexcept
|
|
{
|
|
for (const auto& n : namespaces)
|
|
if (! n.isEmpty())
|
|
return false;
|
|
|
|
return components.size() == 0;
|
|
}
|
|
|
|
int getTotalClassesAndNamespaces() const
|
|
{
|
|
int total = components.size();
|
|
|
|
for (const auto& n : namespaces)
|
|
total += n.getTotalClassesAndNamespaces();
|
|
|
|
return total;
|
|
}
|
|
|
|
void add (const Class& c, const String::CharPointerType& localName)
|
|
{
|
|
auto nextDoubleColon = CharacterFunctions::find (localName, CharPointer_ASCII ("::"));
|
|
|
|
if (nextDoubleColon.isEmpty())
|
|
merge (c);
|
|
else
|
|
getOrCreateNamespace (String (localName, nextDoubleColon))->add (c, nextDoubleColon + 2);
|
|
}
|
|
|
|
bool containsRecursively (const Class& c) const
|
|
{
|
|
if (components.contains (c))
|
|
return true;
|
|
|
|
for (const auto& n : namespaces)
|
|
if (n.containsRecursively (c))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
const Class* findClass (const String& className) const
|
|
{
|
|
for (auto& c : components)
|
|
if (c.getName() == className)
|
|
return &c;
|
|
|
|
for (auto& n : namespaces)
|
|
if (auto* c = n.findClass (className))
|
|
return c;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const MemberInfo* findClassMemberInfo (const String& className, const String& memberName) const
|
|
{
|
|
if (auto* classInfo = findClass (className))
|
|
return classInfo->findMember (memberName);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void findClassesDeclaredInFile (Array<Class*>& results, const File& file)
|
|
{
|
|
for (auto& c : components)
|
|
if (c.isDeclaredInFile (file))
|
|
results.add (&c);
|
|
|
|
for (auto& n : namespaces)
|
|
n.findClassesDeclaredInFile (results, file);
|
|
}
|
|
|
|
void merge (const Namespace& other)
|
|
{
|
|
if (components.size() == 0)
|
|
{
|
|
components = other.components;
|
|
}
|
|
else
|
|
{
|
|
for (const auto& c : other.components)
|
|
merge (c);
|
|
}
|
|
|
|
for (const auto& n : other.namespaces)
|
|
getOrCreateNamespace (n.name)->merge (n);
|
|
}
|
|
|
|
void merge (const Class& c)
|
|
{
|
|
const int existing = components.indexOf (c);
|
|
|
|
if (existing < 0)
|
|
components.add (c);
|
|
else
|
|
components.getReference (existing).mergeWith (c);
|
|
}
|
|
|
|
Namespace* findNamespace (const String& targetName)
|
|
{
|
|
for (auto& n : namespaces)
|
|
if (n.name == targetName)
|
|
return &n;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Namespace* createNamespace (const String& newName)
|
|
{
|
|
namespaces.add (Namespace (newName, fullName + "::" + newName));
|
|
return findNamespace (newName);
|
|
}
|
|
|
|
Namespace* getOrCreateNamespace (const String& newName)
|
|
{
|
|
if (Namespace* existing = findNamespace (newName))
|
|
return existing;
|
|
|
|
return createNamespace (newName);
|
|
}
|
|
|
|
void addInstantiableClasses (SortedSet<Class>& classes) const
|
|
{
|
|
for (const auto& c : components)
|
|
if (c.getInstantiationFlags().canBeInstantiated())
|
|
classes.add (c);
|
|
|
|
for (const auto& n : namespaces)
|
|
n.addInstantiableClasses (classes);
|
|
}
|
|
|
|
void swapWith (Namespace& other) noexcept
|
|
{
|
|
name.swapWith (other.name);
|
|
components.swapWith (other.components);
|
|
namespaces.swapWith (other.namespaces);
|
|
}
|
|
|
|
void nudgeAllCodeRanges (const String& file, int index, int delta)
|
|
{
|
|
for (auto& c : components) c.nudgeAllCodeRanges (file, index, delta);
|
|
for (auto& n : namespaces) n.nudgeAllCodeRanges (file, index, delta);
|
|
}
|
|
|
|
void fileContentChanged (const String& file)
|
|
{
|
|
for (auto& c : components) c.fileContentChanged (file);
|
|
for (auto& n : namespaces) n.fileContentChanged (file);
|
|
}
|
|
|
|
bool matches (const Namespace& other) const
|
|
{
|
|
if (name == other.name
|
|
&& components == other.components
|
|
&& namespaces.size() == other.namespaces.size())
|
|
{
|
|
for (int i = namespaces.size(); --i >= 0;)
|
|
if (! namespaces.getReference (i).matches (other.namespaces.getReference(i)))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void getAllClassNames (StringArray& results, const InstantiationFlags& disallowedFlags) const
|
|
{
|
|
for (const auto& c : components)
|
|
if (c.getInstantiationFlags().isDisallowed (disallowedFlags))
|
|
results.add (c.getName());
|
|
|
|
for (const auto& n : namespaces)
|
|
n.getAllClassNames (results, disallowedFlags);
|
|
}
|
|
|
|
ValueTree toValueTree() const
|
|
{
|
|
ValueTree v (Ids::CLASSLIST);
|
|
|
|
v.setProperty (Ids::name, name, nullptr);
|
|
|
|
for (const auto& c : components) v.appendChild (c.toValueTree(), nullptr);
|
|
for (const auto& n : namespaces) v.appendChild (n.toValueTree(), nullptr);
|
|
|
|
return v;
|
|
}
|
|
|
|
void loadFromValueTree (const ValueTree& v)
|
|
{
|
|
name = v[Ids::name];
|
|
|
|
for (int i = 0; i < v.getNumChildren(); ++i)
|
|
{
|
|
const ValueTree c (v.getChild(i));
|
|
|
|
if (c.hasType (Ids::CLASS))
|
|
components.add (Class (c));
|
|
else if (c.hasType (Ids::CLASSLIST))
|
|
createNamespace (c[Ids::name])->loadFromValueTree (c);
|
|
}
|
|
}
|
|
|
|
bool operator== (const Namespace& other) const noexcept { return name == other.name; }
|
|
bool operator!= (const Namespace& other) const noexcept { return ! operator== (other); }
|
|
bool operator< (const Namespace& other) const noexcept { return name < other.name; }
|
|
|
|
String name, fullName;
|
|
SortedSet<Class> components;
|
|
SortedSet<Namespace> namespaces;
|
|
|
|
JUCE_LEAK_DETECTOR (Namespace)
|
|
};
|
|
|
|
struct ClassList
|
|
{
|
|
ClassList() {}
|
|
|
|
void clear()
|
|
{
|
|
Namespace newNamespace;
|
|
globalNamespace.swapWith (newNamespace);
|
|
}
|
|
|
|
void registerComp (const Class& comp)
|
|
{
|
|
globalNamespace.add (comp, comp.getName().getCharPointer());
|
|
}
|
|
|
|
void merge (const ClassList& other)
|
|
{
|
|
globalNamespace.merge (other.globalNamespace);
|
|
}
|
|
|
|
void swapWith (ClassList& other) noexcept
|
|
{
|
|
globalNamespace.swapWith (other.globalNamespace);
|
|
}
|
|
|
|
//==============================================================================
|
|
ValueTree toValueTree() const
|
|
{
|
|
return globalNamespace.toValueTree();
|
|
}
|
|
|
|
static ClassList fromValueTree (const ValueTree& v)
|
|
{
|
|
ClassList l;
|
|
l.globalNamespace.loadFromValueTree (v);
|
|
return l;
|
|
}
|
|
|
|
Namespace globalNamespace;
|
|
|
|
bool operator== (const ClassList& other) const noexcept { return globalNamespace.matches (other.globalNamespace); }
|
|
bool operator!= (const ClassList& other) const noexcept { return ! operator== (other); }
|
|
|
|
private:
|
|
JUCE_LEAK_DETECTOR (ClassList)
|
|
};
|
|
};
|